Multithreading 数据竞争糟糕吗?
我喜欢解决一个理论计算的争论 假设一切都是0Multithreading 数据竞争糟糕吗?,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()
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
现在,这很有可能是完全正确的。但很可能不是这样
作为程序员,我们有四种可能性:
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
}