从 2 个不同的线程写入共享资源,而不使用同步机制

发布于 2024-11-24 12:41:01 字数 770 浏览 0 评论 0原文

我有以下代码(用 C# 编写,但可以轻松翻译为您喜欢的语言...)

class Program
{
    static int sharedState = 0;

    static void Main(string[] args)
    {
        Thread t1 = new Thread(UpdateState);
        Thread t2 = new Thread(UpdateState);
        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();
        Console.WriteLine("sharedState value is {0}.", sharedState);
    }

    static void UpdateState()
    {
        for (int i = 0; i < 10; i++)
            sharedState++;
    }
}

正如您所猜到的,此代码创建两个工作线程,将共享状态值增加 1,持续 10 次。该代码在访问和写入共享值“sharedState”时没有任何同步机制(例如互斥锁、监视器或任何其他...),并且主线程正在等待两个工作线程完成其工作(Join)。 有人可以向我解释一下这段代码有什么问题吗?最后是否有可能 - 'sharedState' 的值将是 2? (我的一个朋友被问到这个问题,他们告诉他这是可能的,我们都无法理解如何......) 顺便说一句 - 当我运行这段代码时,我每次都会得到 20...

I have the following code (written in c#, but can easily be translated to your prefered language...)

class Program
{
    static int sharedState = 0;

    static void Main(string[] args)
    {
        Thread t1 = new Thread(UpdateState);
        Thread t2 = new Thread(UpdateState);
        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();
        Console.WriteLine("sharedState value is {0}.", sharedState);
    }

    static void UpdateState()
    {
        for (int i = 0; i < 10; i++)
            sharedState++;
    }
}

As you anyone can guess, this code creates two workers threads that increment the shared state value by one for 10 time. The code does not have any synchronization mechanism (such as mutex, monitor or any other...) while accessing and writing the shared value 'sharedState', and the main thread is waiting for the two workers to finish their job (Join).
Does some one can please explain to me what is the problem with this code, and is it possible that at the end - the value of 'sharedState' will be 2? (a friend of mine was asked this question, and they told him that it is possible, and we both cant understand how...)
BTW - when I run this code I get 20 every time...

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

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

发布评论

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

评论(4

三五鸿雁 2024-12-01 12:41:01

线程的寿命不够长。在如此短的循环中,它们同时运行的可能性很小,尤其是在多核计算机上。当您启动第二个线程时,第一个线程已经完成。因为它只需要几分之一微秒,比启动线程所需的时间要少得多。让他们循环至少一千万次,以增加赔率并观看比赛。在发布版本中我的机器上的输出:

sharedState 值为 13952221。

每次运行时都有不同的值。

The threads don't live long enough. The odds that they'll run concurrently are small with such a short loop, especially on a multicore machine. By the time you start the 2nd thread, the 1st thread is already done. Since it takes but a fraction of a microsecond, much less than what it takes to get a thread started. Let them loop for at least ten million times to increase the odds and see the race. Output on my machine in the Release build:

sharedState value is 13952221.

With different values each time I run it.

满栀 2024-12-01 12:41:01

它不是线程安全的,因为

sharedState++;

在内部解释如下:

  • 将值从静态属性推送到计算堆栈
  • 增加计算堆栈中的值 用
  • 计算堆栈中的值替换静态属性中的值

现在,如果此操作不是原子/同步的,您可以拥有更多线程同时从静态属性中获取值,但只会存储最后一个线程的修改。

如果您想要线程安全增量,您应该使用 Interlocked.Increment

It is not thread safe because

sharedState++;

is interpreted internally like:

  • Push the value from static property to evaluation stack
  • Increament the value in evaluation stack
  • Replace the value in static property with value from evaluation stack

Now if this operation is not atomic / synchronized you can have more threads taking the value from the static property concurrently but only modification from the last thread will be stored.

If you want thread safe increment you should use Interlocked.Increment

奈何桥上唱咆哮 2024-12-01 12:41:01
  1. 两个线程都读取该变量,并获取值 0。
  2. 线程 1 设法将变量递增 9 次。
  3. 线程 2 设法将变量递增一次。但它之前认为它是 0,所以它的新值为 1。
  4. 两个线程都读取该变量,并得到值 1。
  5. 线程 2 设法将变量递增 9 次。
  6. 线程 1 设法将变量递增一次。但它之前认为是1,所以它的新值为2。

当然,这在实践中是极不可能的!

  1. Both threads read the variable, and get the value 0.
  2. Thread 1 manages to increment the variable 9 times.
  3. Thread 2 manages to increment the variable once. But it thought it was 0 before, so its new value is 1.
  4. Both threads read the variable, and get the value 1.
  5. Thread 2 manages to increment the variable 9 times.
  6. Thread 1 manages to increment the variable once. But it thought it was 1 before, so its new value is 2.

Of course, this is extremely unlikely in practice!

风轻花落早 2024-12-01 12:41:01

确实有问题。但只有当你非常频繁地运行循环时它才会发生:

假设状态为 1
线程a读取状态并获取1
线程 a 被操作系统暂停,b 正在运行
线程 b 读取状态并获取 1
线程b计算1+1并写入2
线程 b 读取 2
线程b计算2+1并写入3
...
线程a计算1+1并写入2
执行 3 个增量,状态已从 1 更改为 2

但您永远不会得到低于最低边界条件的结果。因此,如果您从 0 开始并且每个线程递增 10 倍,那么无论发生什么情况,您将始终获得 10 或更高。

对于同步:
Interlocked.Increment(参考任何内容)是你的朋友。它是使用汇编程序中的锁定前缀来实现的。这是安全增加值的最有效方法。
使用 CompareEcxchange 可以执行稍微复杂的操作。但lock(something) 也非常高效。

There is a problem, indeed. But it will only accour if you run the loop very very often:

assume the state is 1
thread a reads the state and gets 1
thread a is paused by the os and b is run
thread b reads the state and gets 1
thread b calculates 1+1 and writes 2
thread b reads 2
thread b calculates 2+1 and writes 3
...
thread a calculates 1+1 and writes 2
3 increments where executed and the state has changed from 1 to 2

But you will never ever get a result beeing lower than your lowest boundary condition. So if you start with 0 and each thread increments 10 times you will always get 10 or higher, no matter what happends.

For snychronisation:
Interlocked.Increment(ref whatever) is your friend. It is implemented using the lock-prefix in assembler. It is the most efficient way to safely increment a value.
Slightly more complex operations can be performed using CompareEcxchange. But lock(somthing) is very efficient, too.

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