本文共 3424 字,大约阅读时间需要 11 分钟。
Windows线程及同步机制
有一条原则,即程序中的线程一概不直接操作主线程部分中的GDI。它只要发一个消息给主程序,让主程序来绘制图形,就不会出现任何的问题了。发送消息的方法就是用PostMessage的函数。但一定不能用SendMessage。因为用PostMessage可以让主程序去调度绘图,而SendMesage会立即去绘制图形。所以在线程中要避免画图,因为当作画时,程序会取得一个DC,内存中的DC表示的是一块显存。DC代表的是一个窗口,因为一个程序得到此DC时,其他程序是不能再取得DC的。以后,如果继续再取,就会进入死锁的循环内。
1. 进程与线程同步 同步的意思是一个程序保证在不适宜地被切换时,不会出问题,虽然Windows 3.1有多任务,但没有真正的同步基础,因为这些多任务是协作多过调用API函数(如GetMessage和PeekMessage)。如果一个程序调用了GetMessage或Peekmessage,则意思是说“现在我处在可中断状态”。Win32程序没有这样的协作多任务。它们必须做好随时被CPU切换掉的准备,一个真正的Win32程序不会耗尽CPU时间等待某些事件发生,Win32 API有四个主要的同步对象:
Event 事件
Seqmaphore 信号器
Mutexes 互斥
Critical Section 临界段
除Critical Section外,其余是系统全局对象,并且与不同进程及相同进程中的线程一起工作,这样,同步机也可以用于分离进程的同步活动(同一进程内部的线程除外)。
2. 事件(Event)
这是同步对象的一种类型,正如其名字的含义,在这个中心周围是一些发生在另一个进程或线程中的特殊活动。当你希望线程暂时挂起时,不会消耗CPU的工作周期。事件很类似于我们常用的消息的概念。如果我们剖析消息的内核肯定会发现,它就是用事件来实现的。程序可用CreateEvent或OpenEvent对事件获得一个句柄:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
// pointer to security attributes
BOOL bManualReset, // flag for manual-reset event
BOOL bInitialState, // flag for initial state
LPCTSTR lpName // pointer to event-object name
);
HANDLE OpenEvent(
DWORD dwDesiredAccess, // access flag
BOOL bInheritHandle, // inherit flag
LPCTSTR lpName // pointer to event-object name
);
然后,该程序再调用 WaitForSingleObject,选定事件柄和暂停周期,那么线程就被挂起,一直到其他线程给出事件有关信号后才被再次激活。其他线程指调用SetEvent或PulseEvent所需活动的线程,事件获得这个信号,被挂起的线程即被唤醒并继续执行。
例如,当一个线程要使用另一个线程的排序结果时,你或许希望去使用一个事件。比较糟的方法是执行这个线程并在结束时设置全局变量标志,另一个线程循环检查这个标志是否已设置,这将浪费许多CPU的时间。用事件(Event)做同样的事情则很简单,排序线程在结束时产生一个事件(Event),其他线程调用WaitForSingleObject。这就使得线程被挂起,不浪费CPU周期,当排序线程完成排序时,调用SetEvent唤醒另一个线程继续执行,有效地利用了CPU。
除了WaitForSingleObject外,还有WaitForMultipleObject允许一个线程被挂起,一直到要么满足Event条件,要么有一个等待视窗信息时能恢复,其他挂起的函数一直等到挂起的被满足或I/O操作已经完成时才能,无疑这里体现了灵活性。
3. 信号器(Semaphores)
当你需限制访问特殊资源或限制一段代码到某些线程时,Semaphores非常有用。打一个比喻,就像是餐厅用的餐桌一样,假设这个餐厅有二十个餐桌,当你去时,二十个餐桌都有人在用餐,你就只好等二十个餐桌中有人吃完后才能去用餐,否则你必须等待。在Win32编程中获得Semaphores,就好像得到餐桌的一次控制。为了利用Semaphores,一个线程调用 CreatSemaphore去获得一个HANDLE给Semaphores。该调用包括同时有多少线程使用资源或代码,如果其他线程在另一个进程中,可调用OpenSemaphore去获得一个可利用的HANDLE,当一个线程需要访问共享资源时,要把资源传递给WaitForSingleObject,如果这个Semaphore没有被等待的所有线程请求,等待功能将简单处理Semaphore的使用数,且线程继续执行。换句话说,如果Semaphore已经超出最大值,则调用等待功能的线程将被挂起。一个线程的含义就是使用一个Semaphore来执行,并用ReleaseSemaphore来释放资源。
4. 互斥(Mutexes)
这是同步对象的第三种类型,Mutex(互斥)是“mutual exclusion”的缩略语。一个程序或一组程序希望一次只有一个线程去访问一个资源或一段代码时可使用一次互斥。如果一个线程正在使用这个资源,则另一个线程被排斥在同一资源之外。互斥的用法非常类似于信号器,产生、打开和释放信号器函数都有与互斥类似的内容。当一个线程有互斥要求时,可调用WaitForSingleObject/ WaitForMultipleObjects系列中的函数。用餐桌来比喻的话,就是整个餐厅只有一个餐桌,当有一个人在用餐时,另一个人只能等待用餐。
5. 临界段(Critical Sections)
临界段相当于一个微型的互斥,只能被同一进程中的线程使用。临界段是为了防止多线程同时执行同一段代码。相对其他同步机而言,临界段相对简单和易用,一个临界段可以被认为是仅在单一进程中有效的轻量级互斥。为了使用临界段,一个程序要么分配,要么声明一个CRITICAL_SECTION类型的全局变量。在临界段首次使用之前,其场地需要通过调用InitiazeCriticalSection进行初始化,之后调用EnterCriticalSection将一线程进入临界段了。临界段使用起来很简单,在Windows 95中,当没有其他线程时,如果一个线程线程调用EnterCriticalSection,则只需在CRITICAL_SECTION结构中调整和设置一些场地即可。只有已经存在临界段的另一个线程把EnterCriticalSection调入VMIN 32 VxD时,才能使该线程挂起。
6. WaitForSingleObject/ WaitForMultipleObjects函数
至此,已经概述了线程同步的四种基本方法,我想谈论一下同步线程的其他方法。除了事情、信号器和互斥外,WaitForSingleObject/ WaitForMultipleObjects系列函数可接受几种其他的句柄,把一个进程HANDLE传到一个WaitForSingleObject/ WaitForMultipleObjects函数,则会引起调用线程挂起。如果这个进程已经中止,则Wait函数立即返回。同样,把一个线程的HANDLE传到WaitForSingleObject/ WaitFor Multiple Objects,调用线程也将被挂起。WaitForSingleObject/ WaitForMultipleObjects函数可以挂起的另一个HANDLE是这个文件的变更,之间的变更可以限定一个给定的目录及有选择的子目录。WaitForSingleObject/ WaitForMultipleObjects函数的另外一个HANDLE是一个针对输入装置的HANDLE文件,一旦有未经使用的输入进入输入缓存,Wait函数则返回,并告诉线程继续执行。
转载地址:http://fczvb.baihongyu.com/