何时在线程安全锁定代码中使用'volatile'或'Thread.MemoryBarrier()'?(C#)

Ale*_*lex 16 .net c# multithreading volatile

我什么时候应该使用volatile/Thread.MemoryBarrier()来保证线程安全?

Guf*_*ffa 20

您希望跨线程访问变量而不锁定时使用volatile/ Thread.MemoryBarrier().

像原子这样的变量int总是一次读写整个变量.这意味着在另一个线程更改之前,您将永远不会获得一半的值,而在更改之后,另一半则不会.因此,您可以安全地在不同的线程中读取和写入值,而无需同步.

但是,编译器可能会优化一些读取和写入,您可以使用volatile关键字来阻止这些读取和写入.例如,如果您有一个这样的循环:

sum = 0;
foreach (int value in list) {
   sum += value;
}
Run Code Online (Sandbox Code Playgroud)

编译器实际上可以在处理器寄存器中进行计算,并且仅sum在循环之后将值写入变量.如果你创建sum变量volatile,编译器将生成代码,为每次更改读取和写入变量,以便它的值在整个循环中是最新的.


dtb*_*dtb 10

怎么了?

private static readonly object syncObj = new object();
private static int counter;

public static int NextValue()
{
    lock (syncObj)
    {
        return counter++;
    }
}
Run Code Online (Sandbox Code Playgroud)

这会为您完成所有必要的锁定,内存障碍等.它比基于volatile和的任何自定义同步代码都更好理解和更易读Thread.MemoryBarrier().


编辑

我想不出我会使用volatile或的情景Thread.MemoryBarrier().例如

private static volatile int counter;

public static int NextValue()
{
    return counter++;
}
Run Code Online (Sandbox Code Playgroud)

不是等同于上面的代码,是不是线程安全的(volatile不会使++奇迹般地成为线程安全的).

在这种情况下:

private static volatile bool done;

void Thread1()
{
    while (!done)
    {
        // do work
    }
}

void Thread2()
{
    // do work
    done = true;
}
Run Code Online (Sandbox Code Playgroud)

(应该可以工作)我会使用ManualResetEvent来表示Thread2何时完成.

  • 虽然ECMA规范的内存模型相当薄弱,但您可能需要注意这一点,请参阅http://www.bluebytesoftware.com/blog/2007/11/10/CLR20MemoryModel.aspx和http:// blogs. msdn.com/cbrumme/archive/2003/05/17/51445.aspx (4认同)

Jor*_*oba 10

基本上,如果您使用任何其他类型的同步来使您的代码线程安全,那么您不需要.

大多数锁定机制(包括锁定)自动暗示内存屏障,以便多个处理器可以获得正确的信息.

Volatile和MemoryBarrier主要用于无锁情况,您试图避免锁定的性能损失.

编辑:你应该阅读Joe Duffy关于CLR 2.0内存模型的这篇文章,它澄清了很多东西(如果你真的很感兴趣,你应该阅读Joe Duffie的所有文章,他们是大多数并行的专家. .净)