我尝试在C#中实现同步算法,但没有成功。
为什么以下代码线程不安全?
using System;
using System.Threading;
namespace SoftwareLockTest
{
class Program
{
private static volatile bool _isLocked1 = false;
private static volatile bool _isLocked2 = false;
private static volatile int _count = 0;
static void Main(string[] args)
{
Thread thread2 = new Thread(Thread2Work);
thread2.Start();
Thread1Work();
}
public static void Thread1Work()
{
while (true)
{
_isLocked1 = true;
while (_isLocked2)
{
_isLocked1 = false;
while (_isLocked2) ;
_isLocked1 = true;
}
CriticalSection();
_isLocked1 = false;
}
}
public static void Thread2Work()
{
while (true)
{
_isLocked2 = true;
while (_isLocked1)
{
_isLocked2 = false;
while (_isLocked1) ;
_isLocked2 = true;
}
CriticalSection();
_isLocked2 = false;
}
}
private static void CriticalSection()
{
if (_count != 0)
{
Console.WriteLine("NOT THREAD SAFE 1");
_count = 0;
}
_count++;
if (_count != 1)
{
Console.WriteLine("NOT THREAD SAFE 2");
}
_count--;
if (_count != 0)
{
Console.WriteLine("NOT THREAD SAFE 3");
}
}
}
}
答案 0 :(得分:6)
问题是读取后写入可能会重新排序(即使使用“volatile”)。你需要调用Thread.MemoryBarrier();在四个“while(_isLockedX)”循环前面。
请阅读http://www.albahari.com/threading/part4.aspx以获取有关内存障碍和易失性的解释。
对于任何真实的项目,请选择现有的锁定实现,而不是尝试自己创建。
答案 1 :(得分:5)
您正在尝试实施Dekker's algorithm。不幸的是,当硬件设计师和软件工程师仍然相互交谈时,他生活在更简单的时代。 PC业务供应商之间的激烈竞争,强调速度和核心,对Dekker先生的聪明才智造成了毁灭性的打击。有点高兴,我必须已经对该算法进行了数十次审查,并且从未头疼过。
嗯,这有点儿。查看该Wikipedia文章中的“注释”,了解该算法不再适用的原因。你有很多可行的替代方案。关键的一点是,你发现的有关5年以上并发性的文献不再具有相关性。
答案 2 :(得分:2)
嗯,目前还不完全清楚你试图用锁定标志做什么,并且通过线程理解代码是严重,但是没有lock
/ { {1}},我希望看到增量(Monitor
)/减量(Interlocked
)/ test(.Increment
)有很多.Decrement
。仅仅因为它是.CompareExchange
并不意味着在执行volatile
/ ++
时两个线程不会被绊倒。
但坦率地说,我只会使用--
,除非你有充分的理由不这样做。你想保持简单 - “显然没有错误”,而不是“没有明显的错误”。
答案 3 :(得分:2)
我仍然试图解决这个问题,显然通常你应该确实使用锁...但我怀疑问题是volatile
可能并不意味着什么你认为应该。
我希望您认为这意味着“当我写入此变量时,立即使该写入可见;当我从此变量读取时,读取一个绝对最新的值”。 It doesn't mean quite that(尽管MSDN说的是我自己的线程教程;当我对它有更好的处理时我需要更新它。)
我会看看我是否能确切地知道发生了什么,但看起来确实很奇怪。我想我明白你的代码正在尝试做什么,而且在做出关于波动性的“简单”假设时我还没有设法打破它......(我已经重现了这个问题,这总是有帮助的)
答案 4 :(得分:2)
有趣的是,几个小时前同样的查询在codeguru上了......这是我的回复,改编自那里,但是高位是你需要一个内存栅栏才能让这段代码正常工作。
您的代码的问题在于它依赖于您的系统顺序一致。 x86系统不是SC。它们就是所谓的处理器一致性。
它非常微妙,所以让我们简化你的代码:
thread1:
WRITE _isLocked1, 1
READ _isLocked2
(skip the while loop)
critical section
WRITE _isLocked1, 0
thread2:
WRITE _isLocked2, 1
READ _isLocked1
(skip the while loop)
critical section
WRITE _isLocked2, 0
处理器一致性仅表示thread1按照执行顺序观察线程2完成的写入(因此写入1然后写入0)。相反,线程2观察线程1的写入。什么是咬你的是处理器一致性很少说明thread1的写入如何与thread2的写入交错,除了依赖操作的因果关系被保持(这对你的例子来说并不重要)。
AMD文档第2卷第7.2节有一套很好的例子可以帮助你看到它并引用Dekker的算法以及为什么它需要x86系统上的内存栅栏。
答案 5 :(得分:0)
为什么不简单地使用lock
?
lock (_anyInstantiatedObject)
{
CriticalSection();
}
这样你依靠操作系统来注意没有其他线程同时进入关键部分。