Multithreading 数据竞争糟糕吗?

Multithreading 数据竞争糟糕吗?,multithreading,theory,race-condition,Multithreading,Theory,Race Condition,我喜欢解决一个理论计算的争论 假设一切都是0 Thread0 Thread1 x=1 | y=x 这里我们有一场数据竞赛。据我所知(假设x符合体系结构的字号大小,并与通常的字号边界对齐),结果是x=1^y=0或x=1^y=1 现在,我的第二个示例使用显式锁定(假设lock()获得一些全局锁定),据我所知,这不再是数据竞争条件 Thread0 Thread1 lock() | lock() x=1 | y=x unlock()

我喜欢解决一个理论计算的争论

假设一切都是0

Thread0       Thread1
x=1       |   y=x
这里我们有一场数据竞赛。据我所知(假设x符合体系结构的字号大小,并与通常的字号边界对齐),结果是x=1^y=0或x=1^y=1

现在,我的第二个示例使用显式锁定(假设
lock()
获得一些全局锁定),据我所知,这不再是数据竞争条件

Thread0       Thread1
lock()    |   lock()
x=1       |   y=x
unlock()  |   unlock()

然而,我认为这两个程序是相同的,它们产生相同的输出,具有相同的种族问题。然而,不知何故,人们试图说服我,数据竞争条件很糟糕,我不明白为什么我的第一个程序比第二个程序更糟糕。

如果
x
y
适合机器寄存器,那么默认情况下分配是原子的,所以锁不会改变结果。在第二种情况下,同样可能得到y=0或y=1。

如果
x
y
适合机器寄存器,则默认情况下分配是原子的,因此锁定不会改变结果。在第二种情况下,同样可能得到y=0或y=1。

编辑。维基百科的完整引用如下:

C++11引入了对多线程的形式化支持,并将数据竞争严格定义为非原子变量之间的竞争条件。虽然竞争条件通常会继续存在,但程序员必须避免“数据竞争”,因为如果访问是为了写入,则必须确保一次只有一个线程可以访问任何变量

现在,假设这是正确的(这是维基百科,它在编程方面往往相当不错,但实际上常常是非常错误的),它在这种情况下将“数据竞赛”定义为一种明显的坏情况;可能导致值剪切的值。显然必须避免这种情况,因此必须避免这里定义的数据竞争

根据这个定义,你的问题中的两个程序都没有数据竞争

我的原始答案一般是关于比赛条件的:


第二个例子也有数据竞争。事实上,它的数据竞争与第一次完全相同

这不好吗?那要看情况在任何其他人之前记下。正如我将在下文中详细描述的那样,不仅许多案例都很糟糕,而且那些糟糕的案例往往特别难以发现和修复,这本身就应该让人倾向于假设更糟的情况。

数据竞争不好的一个明显例子是它破坏了数据。假设我们更改您的示例,使
x
y
大于体系结构的字长,我们将
x=-1
。我们还将假设2的补码。现在,
y
的可能值不仅是
-1
0
,而且是
-4294967296
4294967295

在这种情况下,您建议的锁定不会完全删除数据争用,但会删除其中可能导致剪切的部分:
y
的唯一可能值仍然是
-1
0

另一个问题是序列化。通常需要考虑一系列并发事件,作为一系列有限的顺序事件之一。

例如,考虑从<代码> x=0 < /代码>开始,然后具有:

Thread 0    Thread 1
++x         x = -50
现在,这里仍然存在可能导致虚假价值的风险

假设
x
是字号或更小,我们仍然可能有问题。如果操作不是并发的,则有两个可能的值。
x
可以等于
-50
(递增,然后赋值-50),或者
x
可以等于
-49
(赋值-50,然后递增)。但是,同时,我们可能会得到
x
的值为
1
,因为线程0读取
0
,线程1分配
-50
,然后线程0递增并分配
1

现在,这很有可能是完全正确的。但很可能不是这样

作为程序员,我们有四种可能性:

  • 确定数据竞争。确定它是无害的(或相对无害的*),就让它去吧
  • 确定数据竞争。确定它可能导致问题,并进行修复
  • 确定数据竞争。修复它,因为这样我们就不会在确定它是无害的时候犯错误,而实际上它不是
  • 确定数据竞争。确定它可能导致问题。更改代码,使比赛不会引起问题
  • 案例2的重要性是显而易见的——我们将有缺陷的代码转换为没有缺陷的代码

    案例3的重要性取决于时间和可证明性。我们很可能会降低代码的效率(许多停止数据竞争的方法至少有一些开销),但是,删除一个竞争通常比证明它是无害的花费更少的开发人员时间,错误示例的代价是稍微慢一点的代码,而在另一个方向出错的代价是很难修复的错误

    数字1的重要性更为复杂,在一些非常低级的并发代码中,它可以很重要地避免锁定,因此在某些情况下,我们希望容忍竞争。数字4是一种将数字2转化为数字1的方法,当数据竞争是问题固有的(我们无法消除它)或者我们正在进行数字1所涉及的低级别并发时,就会出现

    下面是C#中一个有趣的例子:

    数据竞争应该是显而易见的;在
    theResource
    被设置并且所有CPU的缓存都看到更新之前,我们可能会从不同的线程多次分配给它。这是虫子吗?很多人会说是的,但实际上这要看情况而定。有可能在一段短暂的时间内,我们可以使用不同版本的
    theResource
    public static SomeResource GetTheResource()
    {
      get
      {
        if(_theResource == null)
          _theResource = CreateTheResource();
        return _theResource
      }
    }
    
    internal sealed class LockFreeQueue<T>
    {
      private sealed class Node
      {
        public readonly T Item;
        public Node Next;
        public Node(T item)
        {
          Item = item;
        }
      }
      private volatile Node _head;
      private volatile Node _tail;
      public LockFreeQueue()
      {
        _head = _tail = new Node(default(T));
      }
    #pragma warning disable 420 // volatile semantics not lost as only by-ref calls are interlocked
      public void Enqueue(T item)
      {
        Node newNode = new Node(item);
        for(;;)
        {
          Node curTail = _tail;
          if (Interlocked.CompareExchange(ref curTail.Next, newNode, null) == null)   //append to the tail if it is indeed the tail.
          {
            Interlocked.CompareExchange(ref _tail, newNode, curTail);   //CAS in case we were assisted by an obstructed thread.
            return;
          }
          else
          {
            Interlocked.CompareExchange(ref _tail, curTail.Next, curTail);  //assist obstructing thread.
          }
        }
      }    
      public bool TryDequeue(out T item)
      {
        for(;;)
        {
          Node curHead = _head;
          Node curTail = _tail;
          Node curHeadNext = curHead.Next;
          if (curHead == curTail)
          {
            if (curHeadNext == null)
            {
              item = default(T);
              return false;
            }
            else
              Interlocked.CompareExchange(ref _tail, curHeadNext, curTail);   // assist obstructing thread
          }
          else
          {
            item = curHeadNext.Item;
            if (Interlocked.CompareExchange(ref _head, curHeadNext, curHead) == curHead)
            {
              return true;
            }
          }
        }
      }
    #pragma warning restore 420
    }