class ThreadSyncDemo
    {
        static volatile int sum = 0;  // 静态变量
        static void foo1()
        {
            for (int i = 0; i < 1000000000; ++i)     // 10亿次
            {
                sum++;
            }
            Console.WriteLine("foo1() completed, sum  is " + sum);
        }
        static void foo2()
        {
            for (int i = 0; i < 1000000000; ++i)  // 10亿次
            {
                sum++; 
            }
            Console.WriteLine("foo2() completed, sum  is " + sum);
        }
        // 主函数
        static void Main()
        {            Thread t1 = new Thread(foo1);
            t1.Start();
            Thread t2 = new Thread(foo2);
            t2.Start();
            Console.ReadLine();
        }
    }  大家在自己的电脑上编译运行上面的代码,最后结果是否是20亿???(我的CPU是T9300 双核)
  
  foo1() completed, sum  is 1008290573
  foo2() completed, sum  is 1037423262
  请按任意键继续. . .  能否说说原因

解决方案 »

  1.   

    我的结果是
    foo1() completed, sum is 1971071770
    foo2() completed, sum is 2000000000
      

  2.   

    foo1() completed, sum is 1021431908
    foo2() completed, sum is 1235668282
      

  3.   

    用join 或者lock都可以得到正确数据
      

  4.   

    同疑惑,大概的意思知道,就是说在访问的时候,有volatile标记的是读取内存,没volatile标记的读取寄存器缓存的值,实际中什么情况是明显的这个关键字的用途所在呢?^_^跟楼主一起等答案。
      

  5.   

    线程的执行是乱序的,所以你的foo1和foo2不可能按着顺序完成,也就是说结果20亿相当于foo1和foo2必须全部完成再打印才可能,如果foo1先完成的时候,foo2可能只加了100,结果就回事1000000100,即那十亿的累加加上目前另一个线程完成的100的累加,这样结果就达不到20亿,从你的结果看,你,你的两个线程的确都按照你的本意正常运行了.
      

  6.   

    测试了代码,按照你的写法,表达的应该是,至少一个结果应该是20亿,你用volatile就是为了从虚拟内存中并行读取一个值.
    但是,CLI使用的是纯堆栈的用,所以就算使用vol关键字,你的sum还是得先入栈,就因为这一步,你foo1刚入栈,切换到foo2运行,也入栈并完成计算,这样foo1,foo2假使读取的都是1000,经过foo2运算sum是1001,但是你foo1接着刚才运算还是1001,这样虽然foo1和2都对sum加1,最后内存里的sum不是1002,依旧是1001,这就是为啥有时候达不到20亿的原因.
      

  7.   

    实际上,我也很疑惑,volatile到底什么情况下用的到。
      

  8.   

    应该是多处理器使用各自局部缓存造成不同步造成的。可以在Hyper-Threading单处理器上运行验证下。
      

  9.   

    很感谢大家对这个问题感兴趣,实际上我在很多电脑上运行过这个程序,如果是单核的CPU,最后输出的结果一定是20亿,如果是多核的CPU,结果一般小于20亿。当然,如果加上lock之后,那结果是肯定是等于20亿。我想知道的到底单核和多核CPU在处理volatile问题上的区别而已。
      

  10.   

    你可以找一个Atom或者单个P4 Xeon(2.0~3.8)或者P4 C/E/3.06B/524 电脑来试。这种CPU具备超线程技术,有2个逻辑CPU,有2组寄存器,但是只有1组缓存。
    如果和单线程一样,是2亿,就验证了我的观点。
      

  11.   

    首先你要明白,每CPU/Core任何一个指令周期只可能有一个线程在执行...所以单CPU/Core最后输出的结果一定是20亿,因为线程是轮流执行的不会争用...这个问题应该和超线程无关...超线程只是模拟多线程并行,本质上并没改变...当两个线程同时争用一个资源时,其中一个线程要暂时停止并让出资源,直到资源闲置后才能继续...
      

  12.   

    这也是为什么MSDN上说...volatile字段适用于“只需要执行单个原子操作就能修改”的资源,同时还说...
      

  13.   

    因为sum++不是原子操作。具体解释见下贴2楼:
    http://topic.csdn.net/u/20080821/19/C522CAC6-89C7-434A-92C5-390B4289C889.html
      

  14.   

    要是总是那个结果程序员就轻松了:)
    竞态(racing)是最难重现和调试的。另:严格的说,单核的机器也会出现上述问题(如果线程切换正好发生在特殊点上)。不过这种运气实在是太稀少了。
      

  15.   

    to 9 楼
    实际上,我也很疑惑,volatile到底什么情况下用的到。struct SpinLock
    {
        private volatile int token=0;    public void Enter()
        {
            while(Interlocked.Exchange(ref token, 1)!=0) // spin
        }    public void Exit()
        {
           token=0;
        }
    }
      

  16.   

    我来给答案。
    sum++等同于sun=sum+1,既要先读后加最后赋值三步操作。
    volatile的作用是不保证原子性也不保证顺序性,而是把数据放入内存中,故两函数读该内存数据时不能保证值为最新值。
    例:内存数据值为10,函1读取10,函数2加1后为11,此时内存为11,同时函1还是10,相加后又将数据赋给内存11.