6

我可能在这里遗漏了一些基本的东西,但仍然希望您能帮助理解这一点。因此,我编写了以下简单的多线程程序:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            //            List<int> outcome = new List<int>();
            Test t = new Test();

                Thread thread1 = new Thread(new ThreadStart(t.call1));
                Thread thread2 = new Thread(new ThreadStart(t.call2));
                thread1.Start();
                thread2.Start();
                Thread.Sleep(3000); //Give enough time for threads to end
                Console.Write("{0},", t.mSum);
                t.mSum = 0;
        }
    }

    class Test
    {
        public int mSum = 0;
        public void call1()
        {
            //lock (this)
            //{

            for (int i = 0; i < 100; i++)
            {
                Console.WriteLine("Hello Thread 1, mSum value: {0}", mSum);
                mSum = mSum + 1;
                Console.WriteLine("Goodbye Thread 1, mSum value: {0}", mSum);
            }
            //}
            //  Console.WriteLine(mSum);
        }
        public void call2()
        {
            for (int i = 0; i < 100 ; i++)
            {
                Console.WriteLine("Hello Thread 2, mSum value: {0}",mSum);
                mSum = mSum + 1;
                Console.WriteLine("Goodbye Thread 2, mSum value: {0}",mSum);
            }
        }
    }
}    

所以我希望这个输出是不确定的,因为上下文切换可以随时发生,对吗?但是当我运行程序时,我得到以下输出(只有一部分输出,由于我的 stackoverflow.com 问题发布技巧不佳而导致格式错误):

Hello Thread 1,mSum 值:62 再见 Thread 1,mSum 值:63   
Hello Thread 1,mSum 值:63 再见 Thread 1,mSum 值:64   
你好线程 2,mSum 值:59 再见线程 2,mSum 值:65   
你好线程 2,mSum 值:65 再见线程 2,mSum 值:66

所以,假设我写得对,并且 mSum 确实在线程之间共享(看起来像......) - 我该如何解释第 1 行。3? 线程 2 读取 59,加 1,然后我们得到 65!

我发现了一种新的数学吗?:)

4

4 回答 4

10

您没有锁定共享变量mSummSum = mSum + 1也不是原子操作。而且很明显,打印到控制台,增加一个变量然后再次打印到控制台并不是原子的:)线程可能交错的方式有很多。例如:

1) mSum = 0 [线程 1 正在工作]

2) mSum = 1 [Thread1 正在工作]

3) mSum = 2 [线程 2 正在工作]

4) ...

5) mSum = 59 [Thread2 is working],它在“Hello...”之后被抢占

6) mSum = 60 [线程 1 正在工作]

7) mSum = 61 [线程 1 正在工作]

8) ...

9) mSum = 64 [Thread2 is working] 在增量线 Thread2 继续之前唤醒,并计算 65

即使在5) Thread2从内存中读取 mSum 之后mSum = mSum + 1但在计算mSum + 1.

于 2013-08-23T12:48:38.693 回答
4

因为您正在使用多个线程,所以该值很可能在第一次和第二次Console.WriteLine调用之间发生了变化。

如果您想确保您报告的加法将使用正确的值,则必须使用锁。

于 2013-08-23T12:49:44.433 回答
1

mSum 从第一个 WriteLine 和第二个 WriteLine 之间的 2 个线程 A 和 B 同时更改,mSum 可能会更改。mSum 未锁定或易失,因此您在内存中没有任何障碍,因此您可能会成为一个非常奇怪的结果,但这取决于您的 CPU 类型和现金。

只需将 volatile 这个词放在 mSum 之前,这意味着 mSum 不会在 CPU 中兑现。再次运行应用程序并查看控制台!?

于 2013-08-23T13:00:00.353 回答
0

正如您所指出的,mSum在多个线程之间共享,分辨率要么是锁,要么变成mSum整数volatile

http://msdn.microsoft.com/en-us/library/x13ttww7.aspx

所以你可以改变这个:

public int mSum = 0;

到:

public volatile int mSum = 0;

但正如其他人指出的那样,在更复杂的情况下,您可能想要使用锁。

此外,无需将您的调用包装在 的新实例中ThreadStart,因此请改为:

new Thread(new ThreadStart(t.call1));

你可以这样做:

new Thread(t.call1);
于 2013-08-23T12:51:12.753 回答