WindowsAPI查缺补漏-多线程
WindowsAPI查缺补漏-多线程
线程创建与销毁
CreateThread
在当前进程创建新线程。
1 | HANDLE WINAPI CreateThread( |
例子:
1 | HANDLE hThread; |
SECURITY_ATTRIBUTES
1 | typedef struct _SECURITY_ATTRIBUTES { |
例子:
1 | SECURITY_ATTRIBUTES sa; |
其中线程函数定义格式为:
1 | DWORD WINAPI ThreadProc(LPVOID lpParameter); |
ExitThread
一般就用return
结束线程,这个函数不推荐,是强制线程终止。C++资源如类对象等不会被释放。只能用于终结当前线程。
1 | VOID ExitThread( |
TerminateThread
终结线程,可终止任何线程。这个是异步的,函数返回时不保证已终结完毕,需要WaitForSingleObject
或GetExitCodeThread
检测。被终止的线程是被强制终结的,自己收不到被终止的通知,无法正确清理并结束,且线程自己不能阻止被终结。当不是拥有被终结线程的进程终结时,该线程栈不会被立即销毁,怕其他线程引用就会发生访问违例。
1 | BOOL WINAPI TerminateThread( |
GetExitCodeThread
任何其他线程可通过这个函数检查指定线程是否已终止运行。已终止时返回退出码,尚未终止返回STILL_ACTIVE。
1 | BOOL WINAPI GetExitCodeThread( |
例子
1 |
|
其他线程操作
ResumeThread
创建线程时记录被挂起次数,为0时才会继续运行,被挂起一次加一,被恢复一次减一。当CreateThread
时dwCreationFlags设置CREATE_SUSPENDED时挂起次数为1。
1 | DWORD WINAPI ResumeThread( |
SuspendThread
挂起运行中的函数,挂起计数加一。任何线程可用这个挂起任何线程,也可以挂起自己但显然没法恢复自己了,一个线程可最多被挂起MAXIMUM_SUSPEND_COUNT次,即127次。
1 | DWORD WINAPI SuspendThread( |
Sleep
暂停当前线程指定毫秒数,0表示放弃本线程在当前CPU时间片剩余时间,系统可以转去调度其他线程,待该线程轮流到下一个时间片再继续执行。
1 | VOID WINAPI Sleep( |
线程间通信
简单的有全局变量法,但有同步问题。
自定义消息
用户可以在WM_USER~0x7FFF和WM_APP~0xBFFF之间自定义消息ID。
1 |
|
这个方法不好,不停检查时会导致CPU占用过高。而且如果被挂起的进程在分配堆,则该堆将被锁定,其他访问的进程需要等待,形成死锁。
事件对象
事件对象是内核对象的一种,内核对象还包括互斥量对象、信号量对象、可等待计时器对象、进程对象、文件对象、文件映射对象、I/O完成端口对象、邮件槽对象、管道对象等。
CreateEvent
创建事件对象。
1 | HANDLE WINAPI CreateEvent( |
不存在则创建。已存在则获取句柄,且GetLastError
为ERROR_ALREAD_EXISTS。存在其他同名内核对象则返回NULL,GetLastError
为ERROR_INVALID_HANDLE。无论打开还是创建后都要CloseHandle
。打开或创建后事件对象引用都加一。
SetEvent
设置为有信号。
1 | BOOL WINAPI SetEvent( |
ResetEvent
设置为无信号。自动重置事件在被等待到后自动重置为无信号,手动重置事件需要用这个函数重置。
1 | BOOL WINAPI ResetEvent( |
OpenEvent
打开事件对象。
1 | HANDLE WINAPI OpenEvent( |
没找到返回NULL,GetLastError
为ERROR_FILE_NOT_FOUND;找到同名的其他类型内核对象返回NULL,GetLastError
为ERROR_INVALID_HANDLE;名称相同类型相同的返回事件对象句柄。
WaitForSingleObject
等待指定事件对象变为有信号状态。
1 | DWORD WINAPI WaitForSingleObject( |
返回值:等待的对象变为有信号状态WAIT_OBJECT_0,超时WAIT_TIMEOUT,失败WAIT_FAILED。
WaitForSingleObject
测试多个事件对象的状态。
1 | DWORD WINAPI WaitForMultipleObjects( |
示例
1 |
|
手动重置事件
对于手动重置事件,需要被等待后ResetEvent
重置:
1 |
|
线程间同步
原子访问
x86的不讲了,只讲x64的。以原子方式对多个线程的共享变量进行操作。
1 | LONG InterlockedIncrement64( |
例如:
1 |
|
关键段
把操作共享资源的一段代码保护起来,当一个线程正在执行操作共享资源的这段代码时,其他试图访问共享资源的线程都将被挂起,一直等前一个线程执行完,其他线程才可以执行操作共享资源的代码。
1 | VOID WINAPI InitializeCriticalSection( //初始化关键段对象 |
例如:
1 |
|
SRW锁
跟关键段思想差不多,分为共享模式、独占模式两种。
共享模式时,可以多个进程同时读取某个资源,但不能同时获得写入锁。独占模式时无论读取还是写入都只能一个线程访问。
1 | VOID WINAPI InitializeSRWLock( |
条件变量
例如读取线程和写入线程共用一个缓冲区,写入线程不断向缓冲区写入数据,当缓冲区已满,吸入线程释放关键段或SRW锁对象让自己进入睡眠状态,读取进程就可以获取到关键段或SRW锁对象进行读取操作,读取线程每读取一项就让缓冲区减少一项,并释放关键段或SRW锁对象让自己进入睡眠状态,唤醒写入进程获取关键段或SRW锁对象进行写入操作。总的来说,只要缓冲区未满写入线程不停生产,只要缓冲区不空读取线程不停消费。
InitializeConditionVariable
动态初始化条件变量:
1 | VOID WINAPI InitializeConditionVariable( |
SleepConditionVariableCS/SleepConditionVariableSRW
原子性地释放关键段/SRW锁并进入休眠状态:
1 | BOOL WINAPI SleepConditionVariableCS( |
WakeConditionVariable/WakeAllConditionVariable
唤醒正在条件变量上睡眠地一个/所有线程并重新获取线程进入睡眠状态时释放的关键段/SRW锁对象:
1 | VOID WINAPI WakeConditionVariable( |
例子
1 |
|
互斥量
这东西太慢了,建议用关键段。
CreateMutex
创建一个互斥量对象:
1 | HANDLE WINAPI CreateMutex( |
已有该名互斥量则返回句柄,GetLastError
为ERROR_ALREAD_EXISTS。有同名其他内核对象返回NULL,GetLastError
返回ERROR_INVALID_HANDLE。
ReleaseMutex
释放互斥量对象所有权:
1 | BOOL WINAPI ReleaseMutex( |
OpenMutex
打开一个已经存在地命名互斥量对象:
1 | HANDLE WINAPI OpenMutex( |
例子
1 |
|
信号量
信号量包括最大可用资源计数和当前可用资源计数。例如某个程序有3个工作线程,每个工作线程可处理一个客户端请求。则创建一个最大可用资源计数为3的信号量,初始情况下当前可用资源计数为3,有客户端请求时当前可用资源计数减一,当可用资源计数为0时,其他客户端请求只能处于等待状态。当工作线程处理完请求后释放信号量对象使当前可用资源计数值加一。
CreateSemaphore
创建信号量:
1 | HANDLE WINAPI CreateSemaphore( |
ReleaseSemaphore
释放信号量:
1 | BOOL WINAPI ReleaseSemaphore( |
例子
1 |
|
可等待计时器
可以用于某个时间执行任务,也可以用于每隔一段时间触发一次。
CreateWaitableTimer
创建一个可等待计时器对象:
1 | HANDLE WINAPI CreateWaitableTimer( |
当手动重置寄存器被触发时,正在等待该计时器的所有线程都会变成可调度状态;当自动重置寄存器被触发时,只有一个正在等待该计时器的线程变成可调度状态。
SetWaitableTimer
触发计时器:
1 | BOOL WINAPI SetWaitableTimer( |
其中pDueTime为正数时表示UTC绝对时间。为负数时表示相对时间,单位100纳秒。
lPeriod为正数时表示开启周期模式,直到CancelWaitableTimer
或SetWaitableTimer
重设。设置为0表示一次性的。
CancelWaitableTimer
取消计时器:
1 | BOOL WINAPI CancelWaitableTimer( |
例子
1 |
|