c# 多线程学习笔记(三)原子操作

先由一道问题来说明这个问题。

以下多线程对int型变量x的操作,哪几个不需要进行同步

A. x=y;     B. x++;    C. ++x;   D. x=1;

 

从上一节对“同步”概念的介绍得知,同步是指多个线程对资源的访问保证一定的顺序。

以上四个操作都是对资源x的访问,那么问题相当于是对x访问操作能不能保证有序的。

如果选项是一个不可分离的操作,那么多线程的访问就不需要同步。

 

所谓不可分离的操作步骤,就是原子操作。在多进程线程)访问资源时,能够确保所有其他的进程(线程)都不在同一时间内访问相同的资源。原子操作(atomicoperation)是不需要synchronized,所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 contextswitch (切换到另一个线程)。

 

比如对于B选项,实际是有两步操作累加和赋值。假如有线程1和2都对其进行操作,那么需要保证线程1访问结束后再是线程2访问,或者相反。如果在线程1累加后没有赋值完成,然后线程2就开始操作,那么很显然,线程1的累加结果将不会加上去。最终出来的结果并不是预期的。

C选项同B选项。

 

x86汇编中,对任何内存地址中的1byte的读永远是原子的.也就是说对一个char的读取永远是原子的,对内存地址对齐2byte的int16类型的读取是原子的,对4byte对齐的int32类型读取是原子的,从从奔腾开始,对8byte对齐地址的int64读取是原子的.所以如果你用的是汇编,保证这些就行了。

C/C++中,编译器保证基础类型的内存对齐,例如保证double类型的对齐是8(或者4,忘了),

即使是malloc出来的也可以保证对齐.但是由于各种不可避免的指针转换,例如 char a[4],float* p=(float*)a的存在,使得对齐的保证基本名存实亡.而且,当一个比较长的类型,例如double被编译器放入寄存器的时候,C++标准根本不保证只用一条指令就将它放入一个寄存器中.例如我可以先把前半部分放入eax,等一会儿再把后半部分放入edx等等.不过,如果你能够确保对齐,那么大多数情况下虽然UB,但你的代码还是有可能正常工作的。

通过以上分析,可以得知对于A选项在指令级别也不一定是原子操作。

 

还好windows为我们提供了一些Interlocked开头的函数来屏蔽这些与硬件相关的细节。在c#中这些API都封装在Interlocked类中。

通过一个例子来使用这Interlocked中的API。

模拟统计网站用户登录:每个用户登录用一个线程模拟,线程支行会将一个表示计数的变量递增,程序在最后输出计数的值表示今天有多少用户登录。那么这个值将等于我们启动的线程个数。

我们模拟50个用户,并且重复登录20次。

代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace Counting
{
    class Program
    {
        static void Main(string[] args)
        {
            int MockTime = 20;
            int MockAcount = 50;// the largest count
            AutoResetEvent[] events = new AutoResetEvent[MockAcount];
            while ((MockTime--) != 0)
            {
                Statistic.Init();
                for (int i = 0; i < MockAcount; ++i)
                {
                    events[i] = new AutoResetEvent(false);
                    Thread td = new Thread(Statistic.Sync_Count);
                    td.Start(events[i]); 
                }
                WaitHandle.WaitAll(events);//the number of handles must be less or equal 64
                Console.WriteLine("Total " + Statistic.Count + " Users have finished " + (20 - MockTime).ToString() + " times login in");
            }
        }
    }

    public class Statistic
    {
        static Int32 mCount = 0;
        public static Int32 Count
        {
            get
            {
                return mCount;
            }
        }
        static public void Init()
        {
            mCount = 0;
        }
             
       static public void Sync_Count(object signal)
        {
            Thread.Sleep(100);
            // use the follow to instead the mCount++ to insure the inscrement is a atomic operation
            ++mCount;
            //Interlocked.Increment(ref mCount);
            Thread.Sleep(50);
           // signal that the the work is finished
           ((AutoResetEvent)signal).Set();
        }
    }
}


程序运行结果如下:



我们可以看到这并不是每次都输出50个用户,因此证明语句++mCount是需要同步的。我们将++mCount改成 Interlocked.Increment(refmCount),可以看到期待的结果。

 

我们将用户数改为65,运行程序会发现以下错误

 

原来 WaitHandle.WaitAll 不能超过64。为解决这个问题,我自己实现CustomResetEvent,可以解决这个问题。

然后上面的程序就能实现等待超过65个的用户数。

CustomResetEvent代码如下:

 

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

using System.Threading;
namespace ThreadUtilities
{
    public class CustomResetEvent
    {
        ManualResetEvent mResetEvent;
        Int32 mNumberOfThreadRunning = 1;
        public CustomResetEvent(Int32 threadcount = 1)
        {
            mNumberOfThreadRunning = threadcount;
            mResetEvent = new ManualResetEvent(false);
        }
        public void Wait()
        {
            mResetEvent.WaitOne();
        }
        public void SignalFinishOne()
        {
            if (Interlocked.Decrement(ref mNumberOfThreadRunning) == 0)
                mResetEvent.Set();
        }
    }
}


新的代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using ThreadUtilities;
namespace WaitMoreThan64Thread
{
    class Program
    {
        //ref http://www.codeproject.com/Articles/142341/Solved-The-number-of-WaitHandles-must-be-less-than
        static void Main(string[] args)
        {
            int MockTime = 20;
            while ((MockTime--)!=0)
            {
                int MockAcount = 65;
                CustomResetEvent ResetEvent = new CustomResetEvent(MockAcount);
                Statistic.Init();
                for (int i = 0; i < MockAcount; i++)
                {
                    Thread td = new Thread(new ParameterizedThreadStart(Statistic.Sync_Count));
                    td.Start(ResetEvent);
                }
                ResetEvent.Wait();
                Console.WriteLine("Total " + Statistic.Count + " Users have finished " + (20 - MockTime).ToString() + " times login in");
            }
        }


        public class Statistic
        {
            static Int32 mCount = 0;
            public static Int32 Count
            {
                get
                {
                    return mCount;
                }
            }
            static public void Init()
            {
                mCount = 0;
            }

            static public void Sync_Count(object signal)
            {
                Thread.Sleep(100);
                // use the follow to instead the mCount++ to insure the inscrement is a atomic operation
                Interlocked.Increment(ref mCount);
                Thread.Sleep(50);
                // signal that the the work is finished
                ((CustomResetEvent)signal).SignalFinishOne();
            }
        }
    }
}

结果如下:



参考:

http://blog.csdn.net/morewindows/article/details/7429155

http://www.easyfang.com/life/201508/460858.html

http://baike.baidu.com/link?url=yvZtIPYiYfLkIq9pPdO1JjHdFRzDQUUc0CHLhSbHNgkprg_SBs33LvJ6JsdjiC8h3wKbVg-cKGlNiZvcpRUs6K

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值