windows 线程同步方式

时间:2021-08-21 17:42:06

参考
《windwos 核心编程》
http://baike.baidu.com/view/2808915.htm

1.事件(Event)
特点:适用与多进程,多线程,用户

2.互斥量(Mutex)
特点:多线程

互斥锁的功能和临界区域很相似。
区别是:Mutex所花费的时间比Critical Section多的多,但是Mutex是核心对象(Event、Semaphore也是),可以跨进程使用,而且等待一个被锁住的Mutex可以设定 TIMEOUT,不会像Critical Section那样无法得知临界区域的情况,而一直死等。
MFC中的对应类为CMutex。Win32函数有:创建互斥体CreateMutex() ,打开互斥体OpenMutex(), 释放互斥体ReleaseMutex()。
Mutex的拥有权并非属于那个产生它的线程,而是最后那个对此Mutex进行等待操作 (WaitForSingleObject等等)并且尚未进行ReleaseMutex()操作的线程。
线程拥有Mutex就好像进入Critical Section一样,一次只能有一个线程拥有该Mutex。
如果一个拥有Mutex的线程在返回之前没有调用ReleaseMutex(),那么这个 Mutex就被舍弃了,
但是当其他线程等待(WaitForSingleObject等)这个Mutex时,仍能返回,并得到一个 WAIT_ABANDONED_0返回值。能够知道一个Mutex被舍弃是Mutex特有的。

3.临界区(Critical Section)
特点:多线程、最轻量

临界区域的一个缺点就是:Critical Section不是一个核心对象,无法获知进入临界区的线程是生是死,如果进入临界区的线程挂了,没有释放临界资源,系统无法获知,而且没有办法释放该临界资源。
这个缺点在互斥器(Mutex)中得到了弥补。Critical Section在MFC中的相应实现类是CcriticalSection。CcriticalSection::Lock()进入临界区,CcriticalSection::UnLock()离开临界区。

4.信号量(Semaphores)

信号量是最具历史的同步机制。信号量是解决producer/consumer问题的关键要素。
对应的MFC类是Csemaphore。
Win32函数CreateSemaphore()用来产生信号量。 ReleaseSemaphore()用来解除锁定。
Semaphore的现值代表的意义是目前可用的资源数,如果Semaphore的现值为1,表示还 有一个锁定动作可以成功。如果现值为5,就表示还有五个锁定动作可以成功。
当调用Wait…等函数要求锁定,如果Semaphore现值不为 0,Wait…马上返回,资源数减1。当调用ReleaseSemaphore()资源数加1,当然不会超过初始设定的资源总数。

5.原子访问:Interlocked系列函数

使用场景:
1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
2、互斥量:为协调共同对一个共享资源的单独访问而设计的。
3、信号量:为控制一个具有有限数量用户资源而设计。
4、事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。


Windows下进程和线程同步的四种控制方法 http://blog.csdn.net/chainsmoker2010/article/details/6038678
Windows几种线程同步方法介绍 http://www.cnblogs.com/hlxs/archive/2012/12/30/2840031.html
* * *


InitializeCriticalSectionAndSpinCount、InitializeCriticalSection、InitializeCriticalSectionEx 介绍

InitializeCriticalSectionAndSpinCount 和InitializeCriticalSection(旧IF) 的区别是前者可以设置spin 次数,后者不行。

利用 InitializeCriticalSectionEx 也可以设置spin次数。(新IF)

主要的作用都是初始化一个临界区。

dwSpinCount [in]含义
The spin count for the critical section object. On single-processor systems, the spin count is ignored and the critical section spin count is set to 0 (zero). On multiprocessor systems, if the critical section is unavailable, the calling thread spin dwSpinCount times before performing a wait operation on a semaphore associated with the critical section. If the critical section becomes free during the spin operation, the calling thread avoids the wait operation.

相关系列的还有:SetCriticalSectionSpinCount,EnterCriticalSection, TryEnterCriticalSection, or LeaveCriticalSection
记得最后要DeleteCriticalSection

官方文档
https://msdn.microsoft.com/en-us/library/windows/desktop/ms683476(v=vs.85).aspx

相关资料
https://msdn.microsoft.com/en-us/library/windows/desktop/ms683477(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/ms686908(v=vs.85).aspx


// Global variable
CRITICAL_SECTION CriticalSection; 

int main( void )
{
    ...

    // Initialize the critical section one time only.
    if (!InitializeCriticalSectionAndSpinCount(&CriticalSection, 
        0x00000400) ) 
        return;
    ...

    // Release resources used by the critical section object.
    DeleteCriticalSection(&CriticalSection);
}

DWORD WINAPI ThreadProc( LPVOID lpParameter )
{
    ...

    // Request ownership of the critical section.
    EnterCriticalSection(&CriticalSection); 

    // Access the shared resource.

    // Release ownership of the critical section.
    LeaveCriticalSection(&CriticalSection);

    ...
return 1;
}

InitializeCriticalSectionAndSpinCount 介绍 http://blog.csdn.net/niitlcj/article/details/4996992


The InitializeCriticalSectionAndSpinCount function initializes a critical section object and sets the spin count for the critical section.

BOOL InitializeCriticalSectionAndSpinCount(
  LPCRITICAL_SECTION lpCriticalSection,
                      // pointer to critical section
  DWORD dwSpinCount   // spin count for critical section
);

SetCriticalSectionSpinCountThe SetCriticalSectionSpinCount function sets the spin count for the specified critical section. 
DWORD SetCriticalSectionSpinCount(
  LPCRITICAL_SECTION lpCriticalSection, 
                      // pointer to critical section
  DWORD dwSpinCount   // spin count for critical section
);
  • 当线程试图进入另一个线程拥有的关键代码段时,调用线程就立即被置于等待状态。这意味着该线程必须从用户方式转入内核方式(大约1 0 0 0个C P U周期)。
    这种转换是要付出很大代价的。

  • 因此, InitializeCriticalSectionAndSpinCount 的作用不同于InitializeCriticalSection 之处就在于设置了一个循环锁,
    不至于使线程立刻被置于等待状态而耗费大量的CPU周期,而在dwSpinCount后才转为内核方式进入等待状态。通常dwSpinCount设为4000较为合适 。

  • 实际上对 CRITICAL_SECTION 的操作非常轻量,为什么还要加上旋转锁的动作呢?其实这个函数在单cpu的电脑上是不起作用的,
    只有当电脑上存在不止一个cpu,或者一个cpu但多核的时候,才管用。

  • 如果临界区用来保护的操作耗时非常短暂,比如就是保护一个reference counter,或者某一个flag,那么几个时钟周期以后就会离开临界区。
    可是当这个thread还没有离开临界区之前,另外一个thread试图进入 此临界区——这种情况只会发生在多核或者smp的系统上——发现无法进入,
    于是这个thread会进入睡眠,然后会发生一次上下文切换。我们知道context switch是一个比较耗时的操作,据说需要数千个时钟周期,
    那么其实我们只要再等多几个时钟周期就能够进入临界区,现在却多了数千个时钟周期的开销,真 是是可忍孰不可忍。

  • 所以就引入了InitializeCriticalSectionAndSpinCount函数,它的第一个参数是指向cs的指针,第二个参数 是旋转的次数。
    我的理解就是一个循环次数,比如说N,那么就是说此时EnterCriticalSection()函数会内部循环判断此临界区是否可以进 入,直到可以进入或者N次满。
    我们增加的开销是最多N次循环,我们可能获得的红利是数千个时钟周期。对于临界区内很短的操作来讲,这样做的好处是大大的。

  • MSDN上说,他们对于堆管理器使用了N=4000的旋转锁,然后“This gives great performance and scalability in almost all worst-case scenarios.” 可见还是很有用的:-)