竞争条件究竟是如何工作的?逐步解释给定的例子

How exactly does the race condition work? Explaining step by step on the given example

我想了解竞争条件究竟是如何运作的。具体在这个例子上。该程序的结果是最大值,即 200 000 或小于此值,例如 150 000。所以我的问题是当结果小于 200 000 时它停止计数,它是如何工作的,以及它看起来如何一步一步。我想如果我能在那个例子中理解这一点,它可以帮助我理解关于多线程的一般概念。提前致谢!

using System;
using System.Threading;

class Kontekst
{
    public double x = 0.0;
};

class Watek
{
    public Kontekst kon;
    public int num;

    public Watek(Kontekst kon_, int num_)
    {
        kon = kon_;
        num = num_;
    }

    public void Dzialanie()
    {
        Console.WriteLine("Watek " + num);
        for (int i = 0; i < 100000; ++i) kon.x += 1.0;
    }
};

public class SemaforyPrzyklad
{
    public static void Main(string[] args)
    {
        Kontekst kon = new Kontekst();
        Watek w1 = new Watek(kon, 1);
        Watek w2 = new Watek(kon, 2);
        Thread watek1 = new Thread(w1.Dzialanie);
        Thread watek2 = new Thread(w2.Dzialanie);
        watek1.Start();
        watek2.Start();
        watek1.Join();
        watek2.Join();
        Console.WriteLine("x = " + kon.x);
        Console.ReadKey();
    }
}

如果您还没有,请阅读 What is a race condition? 的答案。

您的示例中的问题在语句 kon.x += 1.0 中。这看起来 , but it's not. The += operator (formally called the Addition assignment operator 不是线程安全的。

如对 Is C# += thread safe? 的回答中所述,kon.x += 1.0 等同于:

var temp = kon.x + 1.0;
kon.x = temp;

这造成了 "check-then-act" 的情况。因为两个线程独立地操作上下文,所以您最终可能会得到如下所示的一系列事件:

// let kon.x = 100
[w1] var temp = kon.x + 1.0; // w1.temp = 101
[w2] var temp = kon.x + 1.0; // w2.temp = 101
[w1] kon.x = temp; // kon.x = 101
[w2] kon.x = temp; // kon.x = 101

两个线程读取相同的初始值 (100) 并递增它,因此我们有效地 "lost" 递增。

在更极端的情况下,一个线程可能比另一个线程工作得更快,从而导致多次丢失增量:

// let kon.x = 100
[w1] var temp = kon.x + 1.0; // w1.temp = 101

[w2] var temp = kon.x + 1.0; // w2.temp = 101

// for some reason, w2 sleeps while w1 completes several iterations
[w1] kon.x = temp; // kon.x = 101
[w1] var temp = kon.x + 1.0; // w1.temp = 102
[w1] kon.x = temp; // kon.x = 102
[w1] var temp = kon.x + 1.0; // w1.temp = 103
[w1] kon.x = temp; // kon.x = 103

// w2 wakes up from its sleep and writes a very old value to kon.x
[w2] kon.x = temp; // kon.x = 101