【问题标题】:Writing to a shared resource from 2 different threads without using sync mechanism在不使用同步机制的情况下从 2 个不同的线程写入共享资源
【发布时间】:2011-07-17 20:14:33
【问题描述】:

我有以下代码(用 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”时没有任何同步机制(例如互斥锁、监视器或任何其他......),主线程正在等待两个worker完成他们的工作(Join)。 有人可以向我解释这段代码有什么问题吗,最后是否有可能 - 'sharedState' 的值是 2? (我的一个朋友被问到这个问题,他们告诉他这是可能的,我们都无法理解如何......) 顺便说一句 - 当我运行这段代码时,我每次都会得到 20...

【问题讨论】:

    标签: c# .net multithreading synchronization thread-safety


    【解决方案1】:

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

    sharedState 值为 13952221。

    每次运行时使用不同的值。

    【讨论】:

      【解决方案2】:

      它不是线程安全的,因为

      sharedState++;
      

      内部解释如下:

      • 将值从静态属性推送到评估堆栈
      • 增加评估堆栈中的值
      • 将静态属性中的值替换为评估堆栈中的值

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

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

      【讨论】:

        【解决方案3】:
        1. 两个线程都读取变量,并得到值 0。
        2. 线程 1 设法将变量递增 9 次。
        3. 线程 2 设法将变量递增一次。但它之前以为是0,所以它的新值是1。
        4. 两个线程都读取变量,并获得值 1。
        5. 线程 2 设法将变量递增 9 次。
        6. 线程 1 设法将变量递增一次。但是它之前认为是1,所以它的新值是2。

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

        【讨论】:

          【解决方案4】:

          确实有问题。但它只会在你非常频繁地运行循环时才会出现:

          假设状态为 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(ref 不管) 是你的朋友。它是使用汇编程序中的锁定前缀实现的。这是安全地增加值的最有效方式。 使用 CompareEcxchange 可以执行稍微复杂的操作。但是 lock(somthing) 也很有效。

          【讨论】:

          • 根据你的回答,线程a没有完成循环,它还有7次迭代,所以'sharedState'的值将超过2。还是我误解了?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-04-01
          • 2015-04-15
          • 1970-01-01
          • 2012-10-16
          相关资源
          最近更新 更多