//1.
利用内核对象进行线程同步的唯一缺点就是他们的性能,在用户模式与内核模式进行切换的时候是很耗时的
//2.
DWORD WINAPI WaitForSingleObject(__in HANDLE hHandle, __in DWORD dwMilliseconds);
https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx
DWORD WINAPI WaitForMultipleObjects( __in DWORD nCount, __in_ecount(nCount) CONST HANDLE *lpHandles, __in BOOL bWaitAll, __in DWORD dwMilliseconds);
https://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx
//3.
事件内核对象:
(A):事件内核对象分为手动重置事件和自动重置事件
手动重置事件:当一个手动重置事件被触发后,所有等待该事件的线程都变为可调度状态
自动重置事件:当一个自动重置事件被触发后,在等待该事件的线程中只有一个线程变为可调度状态
(B):HANDLE WINAPI CreateEvent(__in_opt LPSECURITY_ATTRIBUTES lpEventAttributes, __in BOOL bManualReset, __in BOOL bInitialState, __in_opt LPCWSTR lpName);
bManualReset:TRUE 为手动重置事件, FALSE 为自动重置事件
bInitialState:TRUE 触发状态, FALSE 未触发状态
(C):SetEvent ResetEvent 将事件置为触发/非触发状态
(D):自动重置事件定义了一个等待成功所引起的副作用:当线程成功等到自动重置事件对象的时候,对象会自动变为未触发状态,无需自己手动调用 ResetEvent
(E):PulseEvent:会先触发事件,然后立刻将其恢复到未触发状态
https://msdn.microsoft.com/en-us/library/windows/desktop/ms682396(v=vs.85).aspx
//4.
可等待的计时器内核对象:
(A):此内核对象会在某个指定的时间触发,或每隔一段时间触发一次
(B):HANDLE WINAPI CreateWaitableTimer(__in_opt LPSECURITY_ATTRIBUTES lpTimerAttributes, __in BOOL bManualReset, __in_opt LPCWSTR lpTimerName);
bManualReset:
TRUE 代表可等待的手动重置计时器,当手动重置计时器被触发时,正在等待该计时器内核对象的所有线程都会变为可调度状态
FALSE 代表可等待的自动重置计时器,当自动重置计时器被触发时,所有等待该计时器内核对象的线程中会有一个变为可调度状态
可等待的手动重置计时器:等待成功的副作用是不存在的
可等待的自动重置计时器:等待成功的副作用是会将对应内核对象置为非触发状态
(C):刚创建的可等待的计时器内核对象总是处于非触发状态,当我们想使其变为触发状态时,可以调用 SetWaitableTimer
BOOL
WINAPI
SetWaitableTimer(
__in HANDLE hTimer, //可等待的计时器内核对象句柄
__in const LARGE_INTEGER *lpDueTime, //第一次触发时间,可以是一个绝对时间(此时间可以已逝去),可以是一个相对调用时间(传负值,传入值必须是100纳秒的整数倍)
__in LONG lPeriod, //间隔触发时间,单位为毫秒,传0代表只触发一次
__in_opt PTIMERAPCROUTINE pfnCompletionRoutine, //回调函数
__in_opt LPVOID lpArgToCompletionRoutine, //回调函数参数
__in BOOL fResume //一般为 FALSE
);
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hWaitableTime = CreateWaitableTimer(nullptr, FALSE, nullptr);
LARGE_INTEGER nLargeValue = {};
SYSTEMTIME SystemTime = {};
FILETIME aFileTime[2] = {};
SystemTime.wYear = 2018;
SystemTime.wMonth = 2;
SystemTime.wDay = 6;
SystemTime.wHour = 20;
SystemTime.wMinute = 41;
SystemTime.wSecond = 0;
SystemTime.wMilliseconds = 0;
SystemTimeToFileTime(&SystemTime, aFileTime);
LocalFileTimeToFileTime(aFileTime, aFileTime + 1);
nLargeValue.HighPart = aFileTime[1].dwHighDateTime;
nLargeValue.LowPart = aFileTime[1].dwLowDateTime;
//以上指定一个绝对时间
SetWaitableTimer(hWaitableTime, &nLargeValue, 1000, nullptr, nullptr, FALSE);
while(true)
{
WaitForSingleObject(hWaitableTime, INFINITE);
printf("Hello World\n");
}
CloseHandle(hWaitableTime);
return 0;
//程序每隔1s输出Hello World
}
(D):CancelWaitableTimer:取消一个可等待的计时器内核对象,调用此函数后,可等待的计时器内核对象就不会再被触发了,可以再调用 SetWaitableTimer 进行重设
(E):每次调用 SetWaitableTimer 都会在设置新的触发时间前取消原来的触发时间
(F):可等待的计时器被触发时,当且仅当 SetWaitableTimer 的调用线程处于可提醒状态(此状态细节见第十章), SetWaitableTimer 中指定的回调函数会被同一个线程调用
让可等待的计时器添加APC(异步调用):
void __stdcall FunTest(void* pIn, unsigned long dwTimerLowValue, unsigned long dwTimerHighValue)
{
printf("CallBack:%d\n", ++*static_cast<int*>(pIn));
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hWaitableTime = CreateWaitableTimer(nullptr, FALSE, nullptr);
LARGE_INTEGER nLargeValue = {};
nLargeValue.QuadPart = -1 * 10000000;
int nValue = 0;
SetWaitableTimer(hWaitableTime, &nLargeValue, 1000, FunTest, &nValue, FALSE);
//会在此函数调用后 每隔1s调用 FunTest
while(true)
{
SleepEx(1, TRUE); //此函数 以及APC细节 见第十章
}
CloseHandle(hWaitableTime);
return 0;
}
(G):线程不应该在等待一个可等待的计时器内核对象的同时以可提醒的方式等待同一个可等待的计时器内核对象
HANDLE hTem = CreateWaitableTimer(nullptr, FALSE, nullptr);
SetWaitableTimer(hTem, ..., FunTest, ...);
WaitForSingleObjectEx(hTem, INFINITE, TRUE);
不应该编写如上代码,因为对 WaitForSingleObjectEx 的调用实际上会等待可等待的计时器内核对象两次:一次是可提醒的,另一次是内核对象句柄。对应APC只会产生一次调用
当可等待的计时器内核对象被触发,等待成功,线程被唤醒,这使得线程退出可提醒状态,APC函数不会被调用
void __stdcall FunTest(void* pIn, unsigned long dwTimerLowValue, unsigned long dwTimerHighValue)
{
printf("CallBack:%d\n", ++*static_cast<int*>(pIn));
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hWaitableTime = CreateWaitableTimer(nullptr, FALSE, nullptr);
LARGE_INTEGER nLargeValue = {};
nLargeValue.QuadPart = -1 * 10000000;
int nValue = 0;
SetWaitableTimer(hWaitableTime, &nLargeValue, 1000, FunTest, &nValue, FALSE);
while(true)
{
WaitForSingleObjectEx(hWaitableTime, INFINITE, TRUE);
printf("Hello\n");
//结果是每隔1s先输出 Hello 然后再调用 FunTest 然后再输出 Hello
}
CloseHandle(hWaitableTime);
return 0;
}
https://msdn.microsoft.com/en-us/library/windows/desktop/ms682492(v=vs.85).aspx
//5.
信号量内核对象:可用来对资源进行计数,其内核对象额外包含两个值,一个最大资源计数,一个当前资源计数
信号量内核对象规则:
(A):如果当前资源计数大于0,那么信号量处于触发状态
(B):如果当前资源计数等于0,那么信号量处于未触发状态
(C):当前资源计数不会为负数,也不会大于最大资源计数,这一点由系统保证
(D):创建信号量内核对象:
HANDLE WINAPI CreateSemaphoreW(__in_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, __in LONG lInitialCount, __in LONG lMaximumCount, __in_opt LPCWSTR lpName);
lInitialCount:当前资源计数
lMaximumCount:最大资源计数
(E):
递增信号量的当前资源计数:
BOOL WINAPI ReleaseSemaphore(__in HANDLE hSemaphore, __in LONG lReleaseCount, __out_opt LPLONG lpPreviousCount);
lReleaseCount:递增当前资源计数的量,会将此值加到信号量的当前计数上
lpPreviousCount:返回资源计数的原始值,可以为NULL
无法通过此函数,在不改变当前资源计数的情况下,去获取他的值
(F):等待成功的副作用是当前资源计数减一
//6.
互斥量内核对象用来确保一个线程独占一个资源,互斥量对象包含一个使用计数、线程ID、递归计数
互斥量内核对象的规则:
(A):如果线程ID为0(无效线程ID),那么该互斥量不为任何线程占用,处于触发状态
(B):如果线程ID不为0,那么有一个线程已经占用了该互斥量,他处于未触发状态
(C):假设线程试图等待一个未触发的互斥量对象,通常情况下,此线程将进入等待。但是系统会检查想要获得互斥量对象的线程ID和互斥量内部记录的线程ID是否相同,
如果相同,那么此线程将不会被阻塞,即使该互斥量尚未触发,每次线程成功等待了一个互斥量,互斥量内核对象的递归计数会递增,所以一个线程可以多次等待同一个互斥量内核对象
(D):当占用互斥量内核对象的线程退出,并没有调用 ReleaseMutex ,此时互斥量内核对象就被遗弃了,系统会自动将互斥量内核对象的线程ID设为0,让其变为触发状态, WaitForSingleObject 会返回 WAIT_ABANDONED
(E):
HANDLE WINAPI CreateMutexW(__in_opt LPSECURITY_ATTRIBUTES lpMutexAttributes, __in BOOL bInitialOwner, __in_opt LPCWSTR lpName);
bInitialOwner:表示创建此互斥内核对象的线程是否占有此互斥内核对象
(F):
ReleaseMutex:用于释放互斥量内核对象,会将其递归计数减一。如果递归计数大于1,则此函数也要调用相应次数才能使互斥量触发
当线程为占有互斥量内核对象时,对其调用 ReleaseMutex 会失败
//7.
线程同步对象速查表:
//8.
线程同步函数:
WaitForSingleObject:等待一个内核对象
WaitForMultipleObjects:等待一个或多个内核对象
WaitForInputIdle:会等待其第一个参数标示的进程,直到创建应用程序第一个窗口的线程没有待处理的输入为止
MsgWaitForMultipleObjects(Ex):内核对象或消息都能使得调用线程为触发状态
WaitForDebugEvent:与调试有关
SignalObjectAndWait:通过原子操作触发一个内核对象并等待另一个内核对象,节省内核模式与用户模式切换带来的开销