何时使用“易失性”?或“Thread.MemoryBarrier()”在线程安全锁定代码中? (C#)

发布于 2024-08-03 02:43:00 字数 57 浏览 2 评论 0原文

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

When should I use volatile/Thread.MemoryBarrier() for thread safety?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

人事已非 2024-08-10 02:43:00

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

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

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

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

编译器实际上可能在处理器寄存器中进行计算,并且仅在循环后将值写入 sum 变量。如果将 sum 变量设为 易失性,编译器将生成针对每次更改读取和写入该变量的代码,以便其值在整个循环中保持最新。

You use volatile/Thread.MemoryBarrier() when you want to access a variable across threads without locking.

Variables that are atomic, like an int for example, are always read and written whole at once. That means that you will never get half of the value before another thread changes it and the other half after it has changed. Because of that you can safely read and write the value in different threads without syncronising.

However, the compiler may optimize away some reads and writes, which you prevent with the volatile keyword. If you for example have a loop like this:

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

The compiler may actually do the calculations in a processor register and only write the value to the sum variable after the loop. If you make the sum variable volatile, the compiler will generate code that reads and writes the variable for every change, so that it's value is up to date throughout the loop.

海拔太高太耀眼 2024-08-10 02:43:00

有什么问题吗

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

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

这会为您完成所有必要的锁定、内存屏障等。它比任何基于 volatileThread.MemoryBarrier() 的自定义同步代码更易于理解且更具可读性。


编辑

我想不出使用易失性Thread.MemoryBarrier()的场景。例如,

private static volatile int counter;

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

等价于上面的代码,并且线程安全(易失性不会使++ 神奇地变成线程安全的)。

在这样的情况下:(

private static volatile bool done;

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

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

应该有效)我会使用 ManualResetEvent 在 Thread2 完成时发出信号。

What's wrong with

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

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

?

This does all necessary locking, memory barriers, etc. for you. It's well understood and more readable than any custom synchronization code based on volatile and Thread.MemoryBarrier().


EDIT

I can't think of a scenario in which I'd use volatile or Thread.MemoryBarrier(). For example

private static volatile int counter;

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

is not equivalent to the code above and is not thread-safe (volatile doesn't make ++ magically become thread-safe).

In a case like this:

private static volatile bool done;

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

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

(which should work) I'd use a ManualResetEvent to signal when Thread2 is done.

素食主义者 2024-08-10 02:43:00

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

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

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

编辑:您应该阅读这篇文章< /a> Joe Duffy 撰写的关于 CLR 2.0 内存模型的文章,它澄清了很多事情(如果您真的感兴趣,您应该阅读 Joe Duffie 的所有文章,他是 .NET 并行性方面最专家的人)

Basically if you're using any other kind of synchronization to make your code threadsafe then you don't need to.

Most of the lock mechanisms (including lock) automatically imply a memory barrier so that multiple processor can get the correct information.

Volatile and MemoryBarrier are mostly used in lock free scenarios where you're trying to avoid the performance penalty of locking.

Edit: You should read this article by Joe Duffy about the CLR 2.0 memory model, it clarifies a lot of things (if you're really interested you should read ALL the article from Joe Duffie who is by large the most expert person in parallelism in .NET)

猥︴琐丶欲为 2024-08-10 02:43:00

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

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

As the name implies volatile guarantees that cache value are flushed to memory so that all the threads see the same value. For example, if I have an integer whose latest write is saved in the cache, other threads may not see that. They may even see their cache copy of that integer. Marking a variable as volatile makes it to be read directly from the memory.

Sriwantha Sri Aravinda Attanayake

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文