windows同步和互斥总结

来源:互联网 发布:冰点还原 知乎 编辑:程序博客网 时间:2024/05/17 22:36

一、关键段(CRITICAL_SECTION)(非内核对象)

使用关键段进行线程互斥时非常简单,只涉及到以下四个函数

函数功能:初始化
函数原型:
void InitializeCriticalSection(LPCRITICAL_SECTION   lpCriticalSection);
函数说明:定义关键段变量后必须先初始化。

函数功能:销毁
函数原型:
void DeleteCriticalSection(LPCRITICAL_SECTION   lpCriticalSection);
函数说明:用完之后记得销毁。

函数功能:进入关键区域
函数原型:
void EnterCriticalSection(LPCRITICAL_SECTION   lpCriticalSection);
函数说明:系统保证各线程互斥的进入关键区域。

函数功能:离开关关键区域
函数原型:
void LeaveCriticalSection(LPCRITICAL_SECTION   lpCriticalSection);

typedef struct _RTL_CRITICAL_SECTION {
    PRTL_CRITICAL_SECTION_DEBUG   DebugInfo; //调试用的
    LONG   LockCount; //初始化为-1,n表示有n个线程在等待。
    LONG   RecursionCount; //表示该关键段的拥有线程对此资源获得关键段次数,初为0。
    HANDLE   OwningThread; // from the thread's ClientId->UniqueThread,拥有该关键段的线程句柄
    HANDLE   LockSemaphore;//实际上是一个自复位事件
    DWORD   SpinCount; //旋转锁的设置,单CPU下忽略
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;


由这个结构可以知道关键段会记录拥有该关键段的线程句柄即关键段是有“线程所有权”概念的。事实上它会用第四个参数OwningThread来记录获准进入关键区域的线程句柄,如果这个线程再次进入,EnterCriticalSection()会更新第三个参数RecursionCount以记录该线程进入的次数并立即返回让该线程进入。其它线程调用EnterCriticalSection()则会被切换到等待状态,一旦拥有线程所有权的线程调用LeaveCriticalSection()使其进入的次数为0时,系统会自动更新关键段并将等待中的线程换回可调度状态。

因此可以将关键段比作旅馆的房卡,调用EnterCriticalSection()即申请房卡,得到房卡后自己当然是可以多次进出房间的,在你调用LeaveCriticalSection()交出房卡之前,别人自然是无法进入该房间。

回到这个经典线程同步问题上,主线程正是由于拥有“线程所有权”即房卡,所以它可以重复进入关键代码区域从而导致子线程在接收参数之前主线程就已经修改了这个参数。所以关键段可以用于线程间的互斥,但不可以用于同步。

最后总结下关键段:
1.关键段共初始化化、销毁、进入和离开关键区域四个函数。
2.关键段可以解决线程的互斥问题,但因为具有“线程所有权”,所以无法解决同步问题。
3.推荐关键段与旋转锁配合使用。


二、事件Event

使用事件有关的函数:

第一个 CreateEvent
函数功能:创建事件
函数原型:
HANDLE CreateEvent(
 LPSECURITY_ATTRIBUTES   lpEventAttributes,//参数表示安全控制,一般直接传入NULL。
 BOOL   bManualReset,//手动置位还是自动置位,传入TRUE表示手动置位,传入FALSE表示自动置位。
 BOOL   bInitialState,//事件的初始状态,传入TRUR表示已触发。
 LPCTSTR   lpName//事件的名称,传入NULL表示匿名事件。
);

如果为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。打个小小比方,手动置位事件相当于教室门,教室门一旦打开(被触发),所以有人都可以进入直到老师去关上教室门(事件变成未触发)。自动置位事件就相当于医院里拍X光的房间门,门打开后只能进入一个人,这个人进去后会将门关上,其它人不能进入除非门重新被打开(事件重新被触发)。


第二个 OpenEvent
函数功能:根据名称获得一个事件句柄。
函数原型:
HANDLEOpenEvent(
 DWORD   dwDesiredAccess,//访问权限,对事件一般传入EVENT_ALL_ACCESS
 BOOL   bInheritHandle,  //事件句柄继承性,一般传入TRUE即可
 LPCTSTR   lpName        //名称,不同进程中的各线程可以通过名称来确保它们访问同一个事件。
);


第三个SetEvent
函数功能:触发事件
函数原型:BOOL SetEvent(HANDLE   hEvent);
函数说明:每次触发后,必有一个或多个处于等待状态下的线程变成可调度状态。


第四个ResetEvent
函数功能:将事件设为末触发
函数原型:BOOLResetEvent(HANDLE   hEvent);


最后一个事件的清理与销毁
由于事件是内核对象,因此使用CloseHandle()就可以完成清理与销毁了。


最后总结下事件Event
1.事件是内核对象,事件分为手动置位事件和自动置位事件。事件Event内部它包含一个使用计数(所有内核对象都有),一个布尔值表示是手动置位事件还是自动置位事件,另一个布尔值用来表示事件有无触发。
2.事件可以由SetEvent()来触发,由ResetEvent()来设成未触发。还可以由PulseEvent()来发出一个事件脉冲。
3.事件可以解决线程间同步问题,因此也能解决互斥问题。


三、互斥量Mutex

使用互斥量Mutex主要将用到四个函数

第一个 CreateMutex
函数功能:创建互斥量(注意与事件Event的创建函数对比)
函数原型:
HANDLE   CreateMutex(
  LPSECURITY_ATTRIBUTES   lpMutexAttributes,//表示安全控制,一般直接传入NULL。
  BOOL   bInitialOwner, 
  LPCTSTR   lpName //用来设置互斥量的名称,在多个进程中的线程就是通过名称来确保它们访问的是同一个互斥量。
); //成功返回一个表示互斥量的句柄,失败返回NULL。

BOOL   bInitialOwner, //用来确定互斥量的初始拥有者。如果传入TRUE表示互斥量对象内部会记录创建它的线程的线程ID号并将递归计数设置为1,由于该线程ID非零,所以互斥量处于未触发状态。如果传入FALSE,那么互斥量对象内部的线程ID号将设置为NULL,递归计数设置为0,这意味互斥量不为任何线程占用,处于触发状态。


第二个打开互斥量
函数原型:
HANDLEOpenMutex(
 DWORDdwDesiredAccess, //表示访问权限,对互斥量一般传入MUTEX_ALL_ACCESS
 BOOLbInheritHandle,   //表示互斥量句柄继承性,一般传入TRUE即可。
 LPCTSTRlpName         //表示名称。某一个进程中的线程创建互斥量后,其它进程中的线程就可以通过这个函数来找到这个互斥量。
); //成功返回一个表示互斥量的句柄,失败返回NULL。


第三个触发互斥量
函数原型:
BOOLReleaseMutex (HANDLEhMutex)
函数说明:
访问互斥资源前应该要调用等待函数,结束访问时就要调用ReleaseMutex()来表示自己已经结束访问,其它线程可以开始访问了。

最后一个清理互斥量
由于互斥量是内核对象,因此使用CloseHandle()就可以(这一点所有内核对象都一样)。


互斥量也是有“线程拥有权”概念的。“线程拥有权”在关键段中有详细的说明,这里就不再赘述了。另外由于互斥量常用于多进程之间的线程互斥,所以它比关键段还多一个很有用的特性——“遗弃”情况的处理。比如有一个占用互斥量的线程在调用ReleaseMutex()触发互斥量前就意外终止了(相当于该互斥量被“遗弃”了),那么所有等待这个互斥量的线程是否会由于该互斥量无法被触发而陷入一个无穷的等待过程中了?这显然不合理。因为占用某个互斥量的线程既然终止了那足以证明它不再使用被该互斥量保护的资源,所以这些资源完全并且应当被其它线程来使用。因此在这种“遗弃”情况下,系统自动把该互斥量内部的线程ID设置为0,并将它的递归计数器复置为0,表示这个互斥量被触发了。然后系统将“公平地”选定一个等待线程来完成调度(被选中的线程的WaitForSingleObject()会返回WAIT_ABANDONED_0)。


最后总结下互斥量Mutex:
1.互斥量是内核对象,它与关键段都有“线程所有权”所以不能用于线程的同步。
2.互斥量能够用于多个进程之间线程互斥问题,并且能完美的解决某进程意外终止所造成的“遗弃”问题。


四、信号量Semaphore

信号量Semaphore常用有三个函数
第一个 CreateSemaphore
函数功能:创建信号量
函数原型:
HANDLE CreateSemaphore(
  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, //表示安全控制,一般直接传入NULL。
  LONG lInitialCount, //表示初始资源数量。
  LONG lMaximumCount, //表示最大并发数量。
  LPCTSTR lpName //表示信号量的名称,传入NULL表示匿名信号量。
);


第二个 OpenSemaphore
函数功能:打开信号量
函数原型:
HANDLE OpenSemaphore(
  DWORD dwDesiredAccess, //表示访问权限,对一般传入SEMAPHORE_ALL_ACCESS
  BOOL bInheritHandle, //表示信号量句柄继承性,一般传入TRUE即可。
  LPCTSTR lpName //表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个信号量。
);


第三个 ReleaseSemaphore
函数功能:递增信号量的当前资源计数
函数原型:
BOOL ReleaseSemaphore(
  HANDLE hSemaphore, //信号量的句柄
  LONG lReleaseCount,   //表示增加个数,必须大于0且不超过最大资源数量。
  LPLONG lpPreviousCount //以用来传出先前的资源计数,设为NULL表示不需要传出。
);

注意:当前资源数量大于0,表示信号量处于触发,等于0表示资源已经耗尽故信号量处于末触发。在对信号量调用等待函数时,等待函数会检查信号量的当前资源计数,如果大于0(即信号量处于触发状态),减1后返回让调用线程继续执行。一个线程可以多次调用等待函数来减小信号量。 

最后一个 信号量的清理与销毁
由于信号量是内核对象,因此使用CloseHandle()就可以完成清理与销毁了。


0 0
原创粉丝点击