以下C#代码线程是否安全?

时间:2011-05-12 19:49:59

标签: c# multithreading thread-safety

我正在尝试学习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#内存模型如何处理这个问题?

伙计们,谢谢大家的答案。非常感激。

5 个答案:

答案 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");
}

永远不要将代码放在不需要在锁内的锁中。这是由于这种延迟造成的。如果你有很多线程,你可以通过消除所有这些不必要的等待来获得很多性能。

希望有所帮助:)