Windows 线程同步

Posted by KalosAner on January 24, 2025

一、引言

线程同步类似线程锁,主要用来安全地访问临界资源。Windows 下有两种线程同步的方式,一种是用户模式下的线程同步,一种是内核模式下的线程同步。

二、用户模式

用户模式下的线程同步由于不需要切换内核模式所以性能相对较高,但是功能没有内核模式强大。

用户模式下的线程同步主要使用 CRITICAL_SECTION 对象。

函数原型:

1
2
3
4
5
6
7
#include <windows.h>

void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
void LeaveXriticalSection(LPCRITICAL_SECTION lpCriticalSection);

上面的所有函数都需要传入 CRITICAL_SECTION 对象的地址值。

代码:

SyncCS_win.c

三、内核模式

内核模式下的线程同步有多种实现方法。

基于互斥量对象的同步

创建互斥量对象:

1
2
3
#include <windows.h>

HANDEL CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName);

lpMutexAttributes:安全相关的配置信息,传递 NULL 使用模式安全配置

bInitialOwner:传入 TRUE 则创建出的互斥量对象处于调用该函数的线程,并且进入 non-signaled 状态。传入 FALSE 则创建出的互斥量对象不属于任何线程,并设置为 signaled 状态。

lpName:用于命名互斥量对象,传入 NULL 则创建无名的互斥量对象。

返回值:成功返回互斥量对象句柄,失败返回 NULL。

销毁互斥量对象:

1
2
3
#include <windows.h>

BOOL CloseHandle(HANDLE hMutex);

hMutex:传入需要销毁的互斥量对象句柄

获取互斥量对象:

1
2
3
#include <windows.h>

DWORD WaitForSingleObjext(HANDLE hMutex, DWORD dwMilliseconds);

hMutex:互斥量对象句柄

dwMilliseconds:阻塞 dwMilliseconds 毫秒,传递 INFINITE 则一直阻塞。

返回值:signaled 返回 WAIT_OBJECT_0,超时返回 WAIT_TIMEOUT

释放互斥量对象:

1
2
3
#include <windows.h>

BOOL ReleaseMutex(HANDLE hMutex);

hMutex:传入需要释放的互斥量对象句柄

当一个线程获取互斥量对象时,只有当互斥量处于 signaled 状态时才可以被获取,被获取之后该互斥量对象会进入 non-signaled 状态,当该互斥量被释放时又会转换回 signaled 状态。

代码:

SyncMutex_win.c

基于信号量对象的同步

Windows 中基于信号量对象的同步也与 Linux 下的信号量类似。

创建信号量对象:

1
2
3
#include <windows.h>

HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpNmae);

lpSemaphoreAttributes:安全配置信息,传入 NULL 使用默认安全配置

lInitialCount:信号量的初始值,应大于 0 小于 lMaximumCount

lMaximumCount:信号量的最大值,该值为 1 时类似互斥量

lpNmae:用于命名信号量对象,传入 NULL 时创建无名的信号量对象。

获取信号量与获取互斥量方法一样。

释放信号量对象:

1
2
3
#include <windows.h>

HANDLE ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount);

hSemaphore:需要释放的信号量对象句柄

lReleaseCount:可以指定信号量增加的值,若超过最大值则不增加且返回 FALSE。

lpPreviousCount:需要传入一个变量地址,用于保存之前的值,不需要时传递 NULL。

类似于互斥量,当一个线程获取信号量对象时,只有当信号量处于 signaled 状态时才可以被获取,被获取之后该信号量对象信号量值会减 1,当值为 0 时进入 non-signaled 状态,当该信号量被释放时信号量值增加 1 然后会转换回 signaled 状态。

代码:

SyncSema_win.c

基于事件对象的同步

事件同步对象与前两种同步方法相比有很大不同,区别在于该方式下创建对象时,可以在事件触发时自动切换 non-signaled 状态的 auto-reset 模式(类似于互斥量,但是互斥量有所有权,事件对象没有所有权)和需要手动切换 non-signaled 状态的 manual-reset 模式。

创建事件对象:

1
2
3
#include <windows.h>

HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName);

lpEventAttributes:安全配置信息,传入 NULL 使用默认安全配置

bManualReset:传入 TRUE 创建 manual-reset 模式,传入 FALSE 创建 auto-reset 模式

bInitialState:传入 TRUE 创建 signaled 状态,传入 FALSE 创建 non-signaled 状态

lpName:用于命名信号量对象,传入 NULL 时创建无名的信号量对象。

修改状态:

1
2
3
4
#include <windows.h>

BOOL ResetEvent(HANDLE hEvent);	// to the non-signaled
BOOL SetEvent(HANDLE hEvent);	// to the signaled

代码:

SyncEvent_win.c