多线程数据同步总结

来源:互联网 发布:c语言char*函数 编辑:程序博客网 时间:2024/05/16 23:57

在线程安全的单间类中提到了InterlockedCompareExchangePointer函数及CAS(Compare and swap)技术,或者成为Lock Free,及使用系统提供的基于cpu指令的无锁多线程数据同步技术。这里打算稍微学习并总结下windows平台下常用的多线程数据同步方法以及他们的区别。

多线程同步,最常用的应该是加锁。常见的方式有临界区、信号量、互斥锁等。

临界区是windows下的,是用户态的同步,开销小、速度快,适合用于控制多线程数据访问。不可用于进程间数据同步。首先进入临界区的线程执行,其他线程挂起。

互斥锁在Windows和linux都可用,各线程争抢锁,抢到锁的线程可执行,其他现成挂起等待。互斥锁可以用于线程同步,也可以用于进程同步。工作在内核态,相对来说开销大,速度慢。

信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作。内核态。

和lock free相比,上面的同步方式都先的少重,对于频繁访问的一些资源,如果使用锁来同步,就会有比较大的开销。在某些场景下,使用Lock free,在性能上会有一定的优化。

lock free 基于 CAS(compare and swap)以及atomic。先介绍下一组atomic。

原子操作是不可分割的,在执行完毕不会被任何其它任务或事件中断,不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)

LONG __cdecl InterlockedIncrement(_Inout_  LONG volatile *Addend);//原子+1
LONG __cdecl InterlockedDecrement(_Inout_  LONG volatile *Addend);//原子-1
LONG __cdecl InterlockedExchangeAdd(_Inout_  LONG volatile *Addend,_In_     LONG Value);//原子+n(n可为负数)
LONG __cdecl InterlockedExchange(_Inout_  LONG volatile *Target,_In_     LONG Value);//原子赋值
LONG __cdecl InterlockedCompareExchange(_Inout_  LONG volatile *Destination,_In_     LONG Exchange,_In_     LONG Comparand);//原子比较赋值
PVOID __cdecl InterlockedCompareExchangePointer(_Inout_  PVOID volatile *Destination,_In_     PVOID Exchange,_In_     PVOID Comparand);//原子指针比较赋值

为什么对一个自增操作需要原子操作InterlockedIncrement?

这是因为一条简单的i += 1或者i++指令,在cpu执行时,是由多条指令组合而成的。

1.将i从内存取出,并动态生成一个空间存储取出来的值;
2.将取出来的值和1做加法,并将和放入i的空间覆盖掉原值,操作结束;

起码有两步,那么它就不是原子操作。当有多个线程时,有可能在step1和step2之间由于线程切换而造成数据异常。

而InterlockedIncrement就保证了对一个数i的自增操作是原子的,自动实现线程同步。可以认为InterlockedIncrement内部是这样的,当然,它需要cpu的支持:

InterlockedIncrement(&param)  
{   
      __禁止其他线程访问   (&param)   这个地址   
     param++;          
      move   EAX,  param;   //   设定返回值,C++函数的返回值   都放在EAX中,   
      __开放其他线程访问   (&param)   这个地址   
}   

InterlockedDecrement和InterlockedIncrement类似,他们常用于引用计数的增减。

InterlockedExchangeAdd可以实现+n操作。

InterlockedExchange实现给赋值的原子操作。

InterlockedCompareExchange实现比较赋值,如果destination等于compare,则将exchange的值赋给destination。

InterlockedCompareExchangePointer是对指针的比较赋值。

上面这些原子操作api更多的是在lock free中和其他结合使用。可参看并行编程中的lock free

最后加一句,关于sleep操作。加锁造成的线程等待,线程将被挂起,而sleep操作并不会挂起线程。sleep把进程的运行状态改为睡眠,将其从系统可执行队列去掉,这样系统就不会调度到该进程,不会分配CPU时间片。同时根据该进程的睡眠时间,将进程挂入相应的定时器队列中。同时内核维持一个定时器队列,每一次时钟中断处理,都把当前到期的队列中的进程唤醒,加入到可运行进程队列中。 同时对所有挂入定时器队列中的进程时间值减1。所以即使sleep(0),当前线程也会交出当前cpu时间片中未使用的部分。理解了这些,对于处理CPU占用高的性能问题,使用sleep(0)或sleep(1)会有一定帮助。