我正在尝试学习C#中的线程。今天我在http://www.albahari.com/threading/播种了以下代码:
class ThreadTest
{
bool done;
static void Main()
{
ThreadTest tt = new ThreadTest(); // Create a common instance
new Thread (tt.Go).Start();
tt.Go();
}
// Note that Go is now an instance method
void Go()
{
if (!done) { done = true; Console.WriteLine ("Done"); }
}
}
在Java中,除非您将“done”定义为volatile,否则代码将不安全。 C#内存模型如何处理这个问题?
伙计们,谢谢大家的答案。非常感激。
答案 0 :(得分:7)
嗯,有明显的竞争条件,他们都可以看到done
为假并执行if
身体 - 无论内存模型如何都是如此。使done
volatile不会解决这个问题,也不会在Java中修复它。
但是,是的,在一个线程中进行的更改可能发生但不能可见直到另一个线程。这取决于CPU架构等。作为我的意思的一个例子,考虑这个程序:
using System;
using System.Threading;
class Test
{
private bool stop = false;
static void Main()
{
new Test().Start();
}
void Start()
{
new Thread(ThreadJob).Start();
Thread.Sleep(500);
stop = true;
}
void ThreadJob()
{
int x = 0;
while (!stop)
{
x++;
}
Console.WriteLine("Counted to {0}", x);
}
}
在我的当前笔记本电脑上,这确实终止了,我已经使用了几乎完全相同的代码永远运行的其他机器 - 它永远不会“看到”对stop
的更改在第二个帖子中。
基本上,我试图避免编写无锁代码,除非它使用了真正了解其内容的人提供的更高级别的抽象 - 比如.NET 4中的Parallel Extensions。
使用Interlocked
是一种使这段代码无锁且正确无误的方法。例如:
class ThreadTest
{
int done;
static void Main()
{
ThreadTest tt = new ThreadTest(); // Create a common instance
new Thread (tt.Go).Start();
tt.Go();
}
// Note that Go is now an instance method
void Go()
{
if (Interlocked.CompareExchange(ref done, 1, 0) == 0)
{
Console.WriteLine("Done");
}
}
}
此处,值的更改及其测试是作为一个单元执行的:CompareExchange
只会将值设置为1(如果它当前为0),并将返回旧值。因此,只有一个线程会看到返回值为0。
要记住的另一件事是:你的问题相当模糊,因为你还没有定义“线程安全”的含义。我已经猜测了你的意图,但你从来没有说清楚。阅读this blog post by Eric Lippert - 非常值得。
答案 1 :(得分:5)
不,这不是线程安全的。您可能有一个线程检查条件(if(!done)
),另一个线程检查相同的条件,然后第一个线程执行代码块(done = true
)中的第一行。
您可以通过锁定使其线程安全:
lock(this)
{
if(!done)
{
done = true;
Console.WriteLine("Done");
}
}
答案 2 :(得分:3)
即使在volatile
的Java中,两个线程都可以使用WriteLine
进入块。
如果您想要互斥,则需要使用真正的同步对象,例如锁。
答案 3 :(得分:1)
onle方式这是线程安全的,当你使用原子比较并在if test
中设置时if(atomicBool.compareAndSet(false,true)){
Console.WriteLine("Done");
}
答案 4 :(得分:0)
你应该这样做:
class ThreadTest{
Object myLock = new Object();
...
void Go(){
lock(myLock){
if(!done)
{
done = true;
Console.WriteLine("Done");
}
}
}
你想要使用通用对象而不是“this”的原因是,如果你的对象(又名“this”)发生了变化,那么它就被认为是另一个对象。因此,你的锁不再起作用了。
你可能会考虑另一件小事。这是一个“好习惯”的事情,所以没有什么严重的。
class ThreadTest{
Object myLock = new Object();
...
void Go(){
lock(myLock){
if(!done)
{
done = true;
}
}
//This line of code does not belong inside the lock.
Console.WriteLine("Done");
}
永远不要将代码放在不需要在锁内的锁中。这是由于这种延迟造成的。如果你有很多线程,你可以通过消除所有这些不必要的等待来获得很多性能。
希望有所帮助:)