第九章:用内核对象进行线程同步(一)
2011-10-09 21:42
309 查看
由于第九章笔记比较多,现在分为两篇文章发表
1. 第八章所有的同步函数都是工作在用户模式下,而如果要使用内核模式下的线程同步函数需要耗费一定的时间作为代价.线内核对象的两种状态:
● 触发:对象调度结束
● 未触发:对象正在调度.
其实,进行内核对象的内部有一个布尔变量,当系统创建内核对象的时候会把这个变量的值初始化为FALSE(为触发).当进程终止的时候,操作系统会自动把相应的内核对象中的这个值设为TRUE.表示该对象已经触发.
2. 等待函数使一个线程自愿进入等待状态,直到指定的内核对象被触发为止.需要注意的是,如果线程在调用一个等待函数的时候,相应的内核对象已经处于触发状态,那么线程是不会进入等待状态的.
DWORD WaitForSingleObject(
HANDLE hObject, //标示要等待的内核对象,这个内核对象可以处于触发或未触发状态
DWORD dwMilliseconds);//指定线程最多愿意花多长时间来等待对象被触发.
//如果取值为INFINITE表示线程希望一直等待.知道hObject表示的线程终止为止
函数的返回值:如果等待的对象被触发(成功),那么返回WAIT_OBJECT_0;如果等待超时,那么返回WAIT_TIMEOUT.如果给WaitForSingleObject传入了无效的参数,那么返回值是WAIT_FAILED(可以调用GetLastError来获取详细信息).
如果想要检测多个内核对象是否已触发,可使用WaitForMultipleObject:
DWORD WaitForMultipleObject(
DWORD dwCount,//检测内核对象的数量(取值范围1~64)
CONST HANDLE* phobjects,//指向一个内核对象句柄的数组
BOOL bWaitAll,//等待单个或所有内核对象满足条件(TRUE等待所有内核对象返 //回)
DWORD dwMilliseconds);//同上.
返回值说明:如果bWaitAll为TRUE且所有的对象都被已触发,那么返回WAIT_OBJECT_0,如果为FALSE,那么只要任何一个对象被触发,函数就会立即返回.(返回值范围:WAIT_OBJECT_0到WAIT_OBJECT_0 + dwCount -1之间的值.
3. 等待成功所引起的副作用:当等待的内核对象全部处于触发状态,在函数返回后会使得它重新变为非触发状态.进程和线程内核对象完全没有副作用.WaitForMultipleObject是以原子方式工作的.(这样可以防止发生死锁).
如果多个线程等待同一个内核对象,那么将有系统决定哪个线程最先被唤醒.(其实他的原理就和"先入先出"的机制类似.靠等待时间来唤醒线程).但是如果线程被挂起以后,调度将会被打乱.
4. 事件内核对象:包含一个使用计数、用来表示事件是自动重置事件还是手动重置事件的布尔值),以及另一个用来表示事件有没有被触发的布尔值.
手动重置事件:当一个手动重置事件被触发的时候,正在等待该事件的所有线程都将变成可调度状态.
自动重置事件:只有一个正在等待该事件的线程会变成可调度状态.
事件通常的用途是,让一个线程执行初始化工作,然后再触发另一个线程,让他执行剩余的工作.一开始我们将事件初始化为未触发状态,然后当线程完成初始化的工作时,触发事件,这时另外一个线程一直在等待该事件,它发现事件被触发,于是变成可调度状态.
创建:
HANDLE CreateEvent(
PSECURITY_ATTRIBUTES psa,
BOOL bManualReset,//手动重置事件还是自动重置事件
BOOL bInitialState,//初始化为触发状态还是为触发状态
PCTSTR pszName);
Vista提供的最新函数:
HANDLE CreateEventEx(
PSECURITY_ATTRIBUTES psa,
PCTSTR pszName,
DWORD dwFlags,
DWORD dwDesiredAccess);
其中dwFlags的取值:
WinBase.h中定义的常量 | 描述 |
CREATE_EVENT_INITIAL_SET (0x02) | 等价于CrateEvent中传入的bInitialState参数.如果设置了这个标志,那么函数会将事件初始化为触发状态,否则初始化为未触发状态 |
CREATE_EVENT_MANUAL_RESET (0x01) | 等价于CreateEvent中传入bManualReset参数.如果设置了这个表示,那么创建的是一个手动重置事件,否则创建的是一个自动重置事件. |
一旦创建了事件,我们可以使用SetEvent来直接控制事件的状态.
BOOL SetEvent(HANDLE hEvent);
使用ResetEvent把事件变成未触发状态.
BOOL ResetEvent(HANDLE hEvent).
Microsoft为自动重置事件定义了一个等待成功所引起的副作用:当线程成功等到自动重置事件对象的时候,对象会自动地重置为为触发状态.(也就是说,如果我们想要使用事件来达到同步的话,不该产生自动重置事件,而应该产生手动重置事件).
BOOL PulseEvent(HANDLE hEvent);//会触发事件然后立刻将其恢复到未触发状态.相当于连续调用了SetEvent和ResetEvent函数.
理解:自动重置事件可以想象成就是为了让两个线程互斥访问某个资源.当一个线程启动时,系统再把事件内核对象重置为未触发状态使得其他的线程只能等待当前线程释放.
几个函数讲解:
5. 可等待的计时器内核对象:类似于定时器,在某个指定的时间触发,或每隔一段时间触发一次.通常用来在某个时间执行一些操作.
创建可等待的计时器:
HANDLE CreateWaitableTimer(
PSECURITY_ATTRIBUTES psa,//安全访问属性
BOOL bManualReset,//创建一个手动重置事件还是自动重置时间计时器
PCTSTR pszName);//命名
可以使用OpenWaitanleTimer函数来得到一个已经存在的可等待计时器的句柄.该句柄与当前进程相关联:
HANDLE OpenWaitableTimer(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
当可等待的计时器对象总是处于未触发状态.如果想要触发计时器时,需调用一下函数:
BOOL SetWaitableTimer(
HANDLE hTimer,//句柄
Const LARGE_INTEGER* pDueTime,//计时器第一次触发的时间应该是什么时候
LONG lPeriod,//在第一次触发之后,触发的频率应该是多少
PTIMERAPCROUTINE pfnCompletionRoutine,
PVOID pvArgToCompletionRoutine,
BOOL bResume);//是否挂起,为TRUE表示机器结束挂起模式
◆ 关于时间的一些知识讲解:具体见我的另外两篇转载的博客:
地址1:http://blog.csdn.net/yuanweihuayan/article/details/6853911
地址2:http://blog.csdn.net/yuanweihuayan/article/details/6853914
几者的转换为:
SYSTEMTIME->FILETIME->LARGE_INTEGER
SystemTimeToFileTime(SYSTEMTIME,FILETIME)->LocalFileTimeToFileTime(FILETIME,FILETIME)->然后将其连个为分别赋给LowPart和HighPart两个位即可.
typedef struct _FILETIME {
DWORD dwLowDateTime;
DWORD dwHighDateTime;
} FILETIME, *PFILETIME;
typedef struct _SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;
typedef union _LARGE_INTEGER {
struct {
DWORD LowPart;
LONG HighPart;
};
LONGLONG QuadPart;
} LARGE_INTEGER, *PLARGE_INTEGER;
我们可以给计时器传递一个相对时间.(向pDueTime传一个负值.必须是100纳秒的整数倍)
比如我想要设置计时器在调用函数后的4秒钟后可以给它传递-(4 * 10 000 000)即可
我们也可以设计一个一次性计时器:即向lPeriod传递0即可.
取消计数器:
BOOL CancelWaitableTimer(HANLE hTimer);
如果希望时间一到,把一个异步过程调用(APC)添加到队列中去,可以实现一个APC函数,并传给pfnCompletionRoutine.
VOID APIENTRY TimerAPCRoutine(PVOID pvArgToCompletionRoutine,
DWORD dwTimerLowValue,DWORD dwTimerHighValue){}
计时器被触发时,当且仅当SetWaitableTimer的调用线程正处于可提醒状态时,这个函数会被同一个线程调用.(也就是我们在等待计时器时调用SleepEx,WaitForSingleObjectEx、WaitForMultipleObjectEx、MsgWaitForMultipleObjectEx或者SignalObjectAndWait而进入等待状态).如果线程并非在其中一个函数内等待,那么系统不会把计时器的APC函数添加到队列中.(避免资源浪费).
一般使用步骤如下:
void SomeFun()
{
HANDLE hTimer = CreateWaitableTimer( NULL,TRUE,NULL );
LARGE_INTEGER li = {0};
SetWaitableTimer( hTimer,&li,5000,TimerAPCRoutine,NULL,FALSE );
SleepEx( INFINITE,TRUE );
CloseHandle( hTimer );
}
用户计时器与可等待计时器的区别:
最大的区别是,用户计时器需要在应用程序中使用大量的用户界面基础设施,从而消耗更多的资源,此外可等待计时器时内核对象,意味着它们不仅可以再多个线程间共享,而且可以具备安全性.用户计时器只能是一个线程得到通知,而等待计时器如果是手动的,那么将有多个线程得到通知.
6. 信号量内核对象用来对资源进行计数.组成:
■ 使用计数
■ 另外两个32位值:一个最大资源计数和一个当前资源计数.最大资源计数表示信号量可以控制的最大资源数量,当前资源计数表示信号量当前可用资源的数量.
信号量的规则:
■ 如果当前资源计数大于0,那么信号量处于触发状态
■ 如果当前资源计数等于0,那么信号量处于未触发状态
■ 系统绝对不会让当前资源计数变为负数
■ 当前资源计数绝对不会等于最大资源计数
创建信号量:
HANDLE CreateSemaphore(
PSECURITY_ATTRIBUTES psa,
LONG lInitialCount,//指向开始有多少个可供使用的资源
LONG lMaximumCount,//指向应用程序能够处理的资源的最大数量
PCTSTR pszName);
或者:
HANDLE CreateSemaphore(
PSECUTRY_ATTRIBUTE psa,
LONG lInitialCount,
LONG lMaximumCount,
PCTSTR pszName,
DWORD dwFlags,//系统保留(为0)
DWORD dwDesiredAccess);//访问权限
获取句柄
HANDLE OpenSemaphore(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
递增信号量的当前资源计数:
BOOL ReleaseSemaphore(
HANDLE hSemaphore,
LONG lReleaseCount,//增加的资源数
PLONG plPreviousCount);//当前计数的原始值\
另外不能使用ReleaseSemaphore传给lReleaseSemaphore0或者很大的值来获取当前资源计数
相关文章推荐
- 第九章 使用内核对象进行线程同步
- 第九章:用内核对象进行线程同步
- 第九章:用内核对象进行线程同步(二) .
- <<Windows核心编程(第五版)>>第九章用内核对象进行线程同步:9.3事件内核对象
- 摘自windows核心编程之用内核对象进行线程同步
- Windows核心编程--用内核对象进行线程同步(一)
- (摘自windows核心编程之用内核对象进行线程同步)
- <<windows核心编程>>读书笔记---第9章 内核对象进行线程同步
- (摘自windows核心编程之用内核对象进行线程同步)
- 《Windows核心编程系列》八谈谈用内核对象进行线程同步
- Windows-核心编程-09-如何用内核对象进行线程同步-事件内核对象
- 第9章 用内核对象进行线程同步(1)_事件对象(Event)
- 用内核对象进行线程同步——互斥量内核对象
- Windows-核心编程-09-如何用内核对象进行线程同步-信号内核对象
- 用内核对象进行线程同步——信号量内核对象
- windows核心编程-用内核对象进行线程同步
- windows核心编程---用内核对象进行线程同步
- 用内核对象进行线程同步
- Windows核心编程:(六)用内核对象进行线程同步