为什么这个任务不是线程安全的?

10

我一直在阅读约瑟夫·阿巴哈里(Joseph Albahari)关于线程的书:
http://www.albahari.com/threading/

在第二部分中,我发现了这个例子:
http://www.albahari.com/threading/part2.aspx#_When_to_Lock

这里是上述例子:

class ThreadUnsafe
{
  static int _x;
  static void Increment() { _x++; }
  static void Assign()    { _x = 123; }
}

线程安全版本:

class ThreadSafe
{
  static readonly object _locker = new object();
  static int _x;

  static void Increment() { lock (_locker) _x++; }
  static void Assign()    { lock (_locker) _x = 123; }
}

我不明白为什么Assign方法不是线程安全的。在32位和64位架构上,整数分配不应该是原子操作吗?

1个回答

10

这个赋值操作是原子的,即任何读取线程只会看到123或之前的值 - 不会看到一些中间值。然而,并不保证线程在进行了两次内存屏障(写入线程的写入内存屏障和读取线程的读取内存屏障)之前就能看到新值。

如果您有两个像这样的线程(在将_x公开或设置为内部后,可以从其他类型进行读取 - 或者在ThreadSafe类中使用此代码),则:

// Thread 1
Console.WriteLine("Writing thread starting");
ThreadSafe.Assign();
Console.WriteLine("Writing thread done");


// Thread 2
Console.WriteLine("Reading thread starting");
while (ThreadSafe._x != 123)
{
    // Do nothing
}
Console.WriteLine("Reading thread done");

不能保证线程2会完成,因为线程2可能无法“看到”线程1的赋值。


请您能解释一下这个 while 循环吗?如果线程 1 总是将 123 赋值给 _x,那么线程 2 还能从中读取到什么呢? - Ivan
1
@Ivan:假设第二个线程的代码将值复制到寄存器中,然后继续只使用该寄存器。这在.NET内存模型下是完全合法的。 - Jon Skeet
1
@Coder23:嗯,“全局变量”指的是可以被多个线程访问的任何变量。但是,是的,这就是它的基础。 (顺便说一句,请不要在评论中大声喊叫。) - Jon Skeet
1
@GeorgeMeijer:不,我没有说过那个。它可能在其他线程中没有屏障的情况下可见,但这并不是保证的。(当然,除了互斥锁之外还有很多内存屏障 - 并且有些是隐含的。) - Jon Skeet
1
@GeorgeMeijer:请不要在所有地方都使用太多的粗体,这会使文本变得更难阅读。例如,“感谢回复”没有绝对必要使用粗体,简单的强调可以用斜体更有效地表达。但基本上,现代存储系统比仅仅寄存器要复杂得多。 - Jon Skeet
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接