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

    xiaoxiao2026-03-18  13

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

    以下多线程对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

    转载请注明原文地址: https://ju.6miu.com/read-1308089.html
    最新回复(0)