内核对象进行线程同步
前言:
具体的可等待的内核对象有:
进程,线程,作业,文件以及控制台的标准输入流/输出流/错误流,事件,可等待的计时器,信号量,互斥量。
等待函数:
DWORD WaitForSingleObject( HANDLE hObject,//用来标识要等待的内核对象 DWORD dwMilliseconds);//等待的时间 DWORD WaitForMultipleObjects( DWORD dwCount,//函数检查的内核对象的数量(最大为MAXIMUM_WAIT_OBJECTS) CONST HANDLE* phObjects,//指向一个内核对象句柄的数组 BOOL bWaitAll,//是否等待全部对象都触发 DWORD dwMilliseconds);//等待时间
等待成功引起的副作用:(Windows核心编程)
当等待函数发现对象已经被触发,则称为一个成功的调用,当调用后,对象的状态发生了变化,则称之为等待成功引起的副作用。
正文:
1.事件内核对象(Event)
事件包含一个引用计数,一个用来表示事件是自动重置事件还是手动重置事件的布尔值,以及另一个用来表示事件有没有被触发的布尔值。
HANDLE CreateEvent( PSECURITY_ATTRIBUTES psa, BOOL bManualReset,//TRUE表示手工重置,FALSE表示自动重置 BOOL bInitialState,//TRUE表示触发状态,FALSE表示未触发状态 PCTSTR pszName); HANDLE CreateEventEx( PSECURITY_ATTRIBUTES psa, PCTSTR pszName, DWORD dwFlags,//CREATE_EVENT_INITIAL_SET设置了则初始化为触发状态 //,CREATE_EVENT_MANUAL_RESET设置了则创建一个手工重置事件 DWORD dwDesiredAccess);//指定在创建事件时返回的句柄对事件有何种访问权限 HANDLE OpenEvent( DWORD dwDesiredAccess, BOOL bInherit, PCTSTR pszName); BOOL SetEvent(HANDLE hEvent);//触发事件 BOOL ResetEvent(HANDLE hEvent);//把事件设为未触发状态 BOOL PulseEvent(HANDLE hEvent);//先触发事件后立即恢复到未触发状态
当一个手工重置事件被触发的时候,正在等待该事件的所有线程都将变成可调度状态。当一个自动重置事件被触发的时候,只有一个正在等待该事件的线程会变成可调度状态。
2.可等待的计时器内核对象(Waitable Timer)
可以在某个指定的时间触发,或每隔一段时间触发一次。
HANDLE CreateWaitableTimer( PSECURITY_ATTRIBUTES psa, BOOL bManualReset, PCTSTR pszName); HANDLE OpenWaitableTimer( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName); BOOL SetWaitableTimer( HANDLE hTimer, const LARGE_INTEGER* pDueTime,//表示第一次触发时间在什么时候 LONG lPeriod,//在第一次触发之后,计时器应该以怎么样的频率触发 PTIMERAPCROUTINE pfnCompletionRoutine,//APC PVOID pvArgToCompletionRoutine,//APC参数 BOOL bResume); BOOL CancelWaitableTimer(HANDLE hTimer);//取消计时器
设置可等待计时器时候的pDueTime参数是LARGE_INTEGER*类型的,需要将SYSTEMTIME类型的时间利用SystemTimeToFileTime()转换位FileTime,再将FileTime的成员复制到LARGE_INTEGER结构成员中,然后再将LARGE_INTEGER的地址传给pDueTime。
3.信号量内核对象(Semaphore)
包含一个使用计数,还有一个最大资源计数,一个当前资源计数。
信号量的使用规则如下:
1如果当前的资源计数大于0,那么信号量处于触发状态;
2如果当前的资源计数等于0,那么信号量处于未触发状态;
3系统绝对不会让当前资源计数变为负数
4当前资源计数绝对不会大于最大资源计数
HANDLE CreateSemaphore( PSECURITY_ATTRIBUTE psa, LONG lInitialCount, LONG lMaximumCount, PCTSTR pszName); HANDLE CreateSemaphoreEx( PSECURITY_ATTRIBUTES psa, LONG lInitialCount, LONG lMaximumCount, PCTSTR pszName, DWORD dwFlags,//设为0,系统保留 DWORD dwDesiredAccess);
BOOL ReleaseSemaphore(
HANDLE hSemaphore,
LONG lReleaseCount,
PLONG plPreviousCount);//递增信号量的当前资源计数
信号量以原子方式来执行测试和设置操作,当我们向信号量请求一个资源的时候,操作系统会检查资源是否可用,并将可用资源的数量递减,整个过程不会被别的线程打断。
4.互斥量内核对象(Mutex)
互斥量对象包含一个使用计数、线程ID以及一个递归计数。线程ID用来标识当前占用这个互斥量的是系统中哪个线程,递归计数表示这个线程占用该互斥量的次数。
互斥量的使用规则:
1如果线程ID为0(无效),那么该互斥量不为任何线程所占用,他处于触发状态。
2如果线程ID为非0值,那么有一个线程已经占用了该互斥量,他处于未触发状态
3与所有其他内核对象不同,操作系统对互斥量进行了特殊处理,允许它们违反一些常规的规则(下面说)
HANDLE CreateMutex( PSECURITY_ATTRIBUTES pas, BOOL bInitialOwner,//控制互斥量的初始状态,如果传FALSE,
//那么互斥量对象的线程ID和递归计数都将被设为0(触发状态),
//传TRUE,线程ID为调用线程的线程ID,递归计数将被设为1(未触发状态) PCTSTR pszName); HANDLE CreateMutexEx( PSECURITY_ATTRIBUTES psa, PCTSTR pszName, DWORD dwFlags, DWORD dwDesiredAccess); HANDLE OpenMutex( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName); BOOL ReleaseMutex(HANDLE hMutex);//释放互斥量,递归计数减1
使用方法:
为了获得对被保护资源的访问权,线程要调用一个等待函数并传入互斥量的句柄。在内部,等待函数会检查线程ID是否为0.如果为0,那么函数会把线程ID设为调用线程的线程ID,把递归计数设为1,然后让调用线程继续运行。(相当于关键段的EnterCriticalSection())。
如果等待函数检测到线程ID不为0,那么调用线程将进入等待状态。当另一个线程将互斥量的线程ID设为0的时候,系统会记得有一个线程正在等待,于是它把线程ID设为正在等待的那个线程的线程ID,把递归计数设为1,使正在等待的线程变为可调度状态。
上面所说的互斥量的一切都与其他的内核对象一样,但是互斥量有一些自己的特性:
上面说的违反一些常规规则便是:如果等待函数检测到线程ID不为0但是和目前的调用线程的线程ID相等,那么系统会让线程保持可调度状态——即使该互斥量尚未触发,然后递归计数加1,使递归计数大于1的唯一途径是利用这个例外,让线程多次等待同一个互斥量。
当释放互斥量时ReleaseMutex也会检查调用线程的线程ID与互斥量内部的线程ID是否一致,如果一致,递归计数会递减;如果不一致,那么将返回FALSE,调用GetLastError会返回ERROR_NOT_OWNER。
更多的等待函数:
//等待hProcess标识的进程,直到创建应用程序第一个窗口的线程没有待处理的输入为止。 DWORD WaitForInputIdle( HANDLE hProcess, DWORD dwMilliseconds); //不仅内核对象被触发的是时候调用线程会变成可调度状态,而且当窗口信息需要被派送到一个 //由调用线程创建的窗口时,他们也会变成可调度状态。 DWORD MsgWaitForMultipleObjects( DWORD dwCount, PHANDLE phObjects, BOOL bWaitAll, DWORD dwMilliseconds, DWORD dwWakeMask); DWORD MsgWaitForMultipleObjectsEx( DWORD dwCount, PHANDLE phObjects, DWORD dwMilliseconds, DWORD dwWakeMask, DWORD dwFlags); BOOL WaitForDebugEvent( PDEBUG_EVENT pde, DWORD dwMilliseconds); //原子操作来触发一个内核对象并等待另一个内核对象 DWORD SignalObjectAndWait( HANDLE hObjectToSignal,//只能是互斥量,事件,信号量内核对象 HANDLE hObjectToWaitOn,//可等待的内核对象即可 DWORD dwMilliseconds, BOOL bAlertable);