文|Seraph
一、为什么要“千头万绪”
- 合作型多任务:分享CPU是程序的责任(而非操作系统),所以需要各个程序相互合作,不能一个程序咬住CPU不放。
- 一个永远有反应的UI是很重要的。
- 进程本身并不能够执行,它只是提供一个安置内存和线程的地方。
- 在Unix中,进程与其主线程是相同的东西。
- “两个线程同时计算PI”所花费的时间比“一个线程接连做两次一样的工作”所花费的时间长一点,因为需要上下文切换。所以说,多线程的好处不是缩短了总的执行时间,而是新的执行流程,同时也高效的利用CPU的空闲时间。
- 多线程给我们带来了很多好处,但是也会引发很多潜在性的严重问题,所以我们需要着重关注多线程的设计。
二、线程的第一次接触
-
#define WINAPI __stdcall
(windef.h定义)。 - 在编译时,使用/MT或/MD选项,表示使用多线程版本的C runtime library。
- 一般重导至文件中比屏幕上输出更快些。
- 为了安全起见,你不可能根据一个线程的ID而获得其Handle。
- Win32核心对象
中文名称 | 英文 |
---|---|
进程 | processes |
线程 | threads |
文件 | files |
事件 | events |
信号量 | semaphores |
互斥器 | mutexes |
管道 | pipes(分为named和anonymous) |
- GDI对象有单一拥有者,不是进程就是线程。核心对象可以有一个以上的拥有者,甚至可以跨进程。
- CloseHandle重要性,使核心对象引用减一(核心对象只有在引用计数为0时,才会被清理)
- worker线程,是指完全不牵扯到GUI,纯粹做运算的线程。
- 多线程设计:简单和安全,更甚于复杂和速度。
- 最低表面积,意指必须被线程共享的数据结构。这是我们多线程设计希望达到的目录之一。
- 多线程的数据块,最好是用heap。全局变量容易被修改,堆栈变量容易超过生成范围。
- 多线程使用数据时,不要直接读取界面或者随时可能被改变的值,可以复制一份过来,再进行处理。意思是,当worker线程启动后,它并不需要任何来自对话框的信息或全局变量。
- 多线程设计:
- 各线程数据要分离开来,避免使用全局变量
- 不要在线程之间共享GDI对象
- 确定你知道你的线程状态看,不要径自结束程序而不等待它们的结束
- 让主线程处理用户界面
三、快跑与等待
四、同步控制
- SendMessage是同步行为,PostMessage是异步行为
- Critical Section的几个关键问题
- Critical Section不是核心对象,没有所谓的handle
- 如果进入Critical Section那个线程结束掉或当掉了,而没有调用LeaveCriticalSection的话,系统没办法将该Critical Section清除掉。
- 不要长时间锁住一份资源
- 当使用同步机制保护一份资源时,一定考虑好线程必须多快释放这份资源,才能确保整个系统运行很平顺。
- 常用防止死锁的办法:All-or-nothing
- Mutex的几个关键问题
- mutex对象对整个系统而言是全局的
- CreateMutex指定已经存在的mutex名称,会返回原mutex handle
- 如果线程拥有一个mutex而在结束前没有调用ReleaseMutex,则下一个等待中的线程会被以WAIT_ABANDONED_0通知(表示被舍弃)
- CreateMutex时,赋值TRUE可立即拥有mutex,而不会可能出现其他线程抢占
- mutex与Critical Section比较
mutex | Critical Section |
---|---|
核心对象花费时间长 | 非核心对象花费时间短 |
跨进程 | 同进程 |
可指定结束等待时间 | 不可指定 |
- Semaphores的几个关键问题
- Semaphores不像mutexes,它并没有所谓的“wait abandoned”状态可以被其他线程侦测到
- 任何线程都可以在任何时间调用ReleaseSemaphore
- ReleaseSemaphore函数的参数lpPreviousCount所传回来的是一个瞬间值,不可把lReleaseCount加上*lpPreviousCount就当做semaphore的现值
- Event几个关键问题
- Event有别于其他对象的是,它的状态完全在你掌控之中
- SetEvent与PulseEvent区别:在自动重置模式下,是等同的;在手动重置的模式下,PulseEvent把Event对象设为激发状态,唤醒“所有”等待中的线程,然后Event恢复为非激活状态
五、 不要让线程成脱缰野马
- 终止一个线程的方法:设立一个标志(例如主线可操作的事件),在线程种用WaitForSingleObject等待退出事件激活。
- 线程优先级一般是进程叠加或减去。
- 优先级很难控制,尽量不要自己去控制优先级。
- 启动一个线程之前,可以使用挂起,初始化线程后,再启动。
网友评论