先由一道问题来说明这个问题。
以下多线程对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