如果我在 x86(32 位)模式(最近的 Windows 11 计算机上的 .NET Framework 4.7.2)下运行以下代码,那么,正如预期的那样,它会写入控制台(证明读出的数据不是其中之一)由于撕裂而写入两个值:
这是预料之中的。根据这些参考资料,以及 Lippert 在 https://ericlippert.com/2012/12/20/nullable-micro-optimizations-part-one/ 的评论,我预计
Nullable<long>
会 also 表现出撕裂.
但是,它没有(取消注释下面的代码以进行重现)。现在,当然,我确信这是合法的。事实上,他们告诉你某件事不安全,并不意味着它肯定不起作用——它起作用是完全有效的。但我很好奇为什么没有发生撕裂......
有没有办法导致撕裂
Nullable<long>
?
using System;
using System.Threading.Tasks;
namespace ConsoleApp2
{
public class Program
{
public static void Main(string[] args)
{
long bob = 0;
//long? bob = 0;
long small = 0;
long large = long.MaxValue;
Task.Run(() =>
{
do
{
bob = small;
bob = large;
} while (true);
});
for (long i = 0; i < long.MaxValue - 2; i++)
{
var value = bob;
if (value != small && value != large)
{
Console.WriteLine("very bad");
}
}
Console.WriteLine("finished");
Console.ReadLine();
}
}
}
有没有办法导致 Nullable 撕裂?
是的。
通过这段代码,我们在两个线程之间引入了重复的竞争。在一个线程中,我们将值设置为
null
,在另一个线程中将值设置为 2
。
public struct MyNullable<T> where T : struct {
public bool hasValue;
public T value;
public override string ToString() {
return hasValue + " " + value.ToString();
}
}
static void Main() {
int tries = 10_000_000;
long? l = 0;
var listTears = new List<long?>();
Action<Barrier> postPhase = _ => {
var cast = Unsafe.As<long?, MyNullable<long>>(ref l);
if (cast.hasValue == false && cast.value == 2) {
var capture = l;
listTears.Add(capture);
l = 0; // reset for next race
}
};
var barrier = new Barrier(2, postPhase);
var listOfThreads = new List<Thread>();
var t1 = new Thread(() => {
for (int j = 0; j < tries; j++) {
l = null;
barrier.SignalAndWait();
}
});
var t2 = new Thread(() => {
for (int j = 0; j < tries; j++) {
l = 2;
barrier.SignalAndWait();
}
});
t1.Start();
t2.Start();
t1.Join();
t2.Join();
var countTears = listTears.Count;
Console.WriteLine($"Struct tears: {countTears}");
var takeTenTears = listTears
.Take(10)
.Select(x => Unsafe.As<long?, MyNullable<long>>(ref x));
foreach (var element in takeTenTears) {
Console.WriteLine(element.ToString());
}
// False 2
//False 2
//False 2
//False 2
//False 2
//False 2
//False 2
//False 2
//False 2
//False 2
}