17

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

4

4 回答 4

20

当您想跨线程访问变量而不锁定时,您可以使用volatile/ 。Thread.MemoryBarrier()

原子变量,例如一个int例子,总是被一次性读取和写入。这意味着在另一个线程更改它之前你永远不会得到一半的值,而在它改变之后你永远不会得到另一半。因此,您可以在不同线程中安全地读取和写入值而无需同步。

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

sum = 0;
foreach (int value in list) {
   sum += value;
}

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

于 2009-08-25T20:21:22.040 回答
11

有什么问题

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

public static int NextValue()
{
    lock (syncObj)
    {
        return counter++;
    }
}

?

这将为您完成所有必要的锁定、内存屏障等。它比任何基于volatileThread.MemoryBarrier().


编辑

我想不出我会使用volatileor的场景Thread.MemoryBarrier()。例如

private static volatile int counter;

public static int NextValue()
{
    return counter++;
}

不等同于上面的代码,也不是线程安全的(不会神奇地变成线程安全的)。volatile++

在这样的情况下:

private static volatile bool done;

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

void Thread2()
{
    // do work
    done = true;
}

(应该可以)当 Thread2 完成时,我会使用ManualResetEvent发出信号。

于 2009-08-25T20:02:33.543 回答
11

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

大多数锁机制(包括锁)会自动隐含一个内存屏障,以便多个处理器可以获取正确的信息。

Volatile 和 MemoryBarrier 主要用于无锁场景中,您试图避免锁定的性能损失。

编辑:您应该阅读Joe Duffy 撰写的关于 CLR 2.0 内存模型的这篇文章,它阐明了很多事情(如果您真的感兴趣,您应该阅读 Joe Duffie 的所有文章,他是。网)

于 2009-08-25T20:02:51.220 回答
0

顾名思义,易失性保证缓存值被刷新到内存中,以便所有线程看到相同的值。例如,如果我有一个整数,其最新写入保存在缓存中,其他线程可能看不到。他们甚至可能会看到该整数的缓存副本。将变量标记为易失性使其可以直接从内存中读取。

斯里万塔·斯里·阿拉文达·阿塔纳亚克

于 2011-09-04T14:34:31.420 回答