第14章MFC 多线程程序设计
1. 三个观念:模块、进程、执行线程
一段可执行的程序(包括EXE 和DLL),其程序代码、资料、资源被加载到内存中,由系统建置一个数据结构来管理它,就是一个模块。
进程主要表达「拥有权」的观念,执行线程则主要表达模块中的程序代码的「执行事实」。系统也是以一个特定的数据结构(Thread Database,TDB)记录执行线程的所有相关资料,包括执行线程区域储存空间(Thread Local Storage,TLS)、讯息队列、handle 表格、地址空间(MemoryContext)等。
2. 执行线程排程(Scheduling)
排程器挑选「下一个获得CPU 时间的执行线程」的唯一依据就是:执行线程优先权。
对于那些优先权本来就高的执行线程,也并不是有永久的保障权利。别忘了Windows 毕竟是个消息驱动系统,如果某个执行线程调用::GetMessage 而其消息队列却是空的,这个执
行线程便被冻结,直到再有消息进来为止。冻结的意思就是不管你的优先权有多高,暂时
退出排班行列。执行线程也可能被以::SuspendThread强制冻结住(::ResumeThread 可以解
除冻结)。
会被冻结,表示这个执行线程「要去抓取消息,而执行线程所附带的消息队列中却没有消息」。
如果一个执行线程完全和UI 无关呢?是否它就没有消息队列?倒不是,但它的程序代码中
没有消息循环倒是事实。是的,这种执行线程称为workerthread。正因它不可能会被冻结,
所以它绝对不受Win16Mutex 或其它因素而影响其强制性多任务性质,及其优先权。
3. Worker Threads 和 UI Threads
从Windows 操作系统的角度来看,执行线程就是执行线程,并未再有什么分类。但从MFC
的角度看,则把执行线程划分为和使用者接口无关的workerthreads,以及和使用者接口
(UI)有关的UI threads。
基本上,当我们以::CreateThread 产生一个执行线程,并指定一个执行线程函数,它就是一
个worker thread,除非在它的生命中接触到了输入消息-- 这时候它应该有一个消息回
路,以抓取消息,于是该执行线程摇身一变而为UIthread。
4. MFC 多线程程序设计
A. 就像CWinApp 对象代表一个程序本身一样,CWinThread 对象代表一个执行线程本身。
B. 虽然MFC 程序只会有一个CWinApp 对象,而CWinApp 衍生自CWinThread,但并不
是说一个MFC 程序只能有一个CWinThread 对象。每当你需要一个额外的执行线程,不
应该在MFC 程序中直接调用::CreateThread 或_beginthreadex,应该先产生一个
CWinThread 对象,再调用其成员函数CreateThread 或全域函数AfxBeginThread将执行
线程产生出来
C. 产生一个Worker Thread
CWinThread*pThread = AfxBeginThread(ThreadFunc, &Param);
...
UINTThreadFunc(LPVOID pParam)
{
...
}
执行线程函数是由系统调用的,也就是个callback函数,不容许有this 指针参数。所以任何一般的C++ 类别成员函数都不能够拿来当做执行线程函式。它必须是个全域函数,或是个C++ 类别的static 成员函数。
D. 产生一个UI Thread
UI thread 可不能够光由一个执行线程函数来代表,因为它要处理消息,它需要一个消息回
路。好得很,CWinThread::Run 里头就有一个消息循环。所以,我们应该先从CWinThread
衍生一个自己的类别,再调用AfxBeginThread产生一个CWinThread 对象:
E. 执行线程的结束
既然worker thread 的生命就是执行线程函数本身,函数一旦return,执行线程也就结束了,
自然得很。或者执行线程函数也可以调用AfxEndThread,结束一个执行线程。
不论worker thread 或UI thread,都需要一个CWinThread 对象,当执行线程结
束,记得把该对象释放掉(利用delete)。
F. 执行线程与同步控制
Windows 系统提供四种同步化机制,帮助程序进行这种工作:
1. Critical Section(关键区域)
2. Semaphore(号志)
3. Event(事件)
4. Mutex(Mutual Exclusive,互斥器)