不同的对象具有不同的线程亲缘性规则,但其基本原则来自古老的 16 位 Windows。
在 Windows 系统上,最重要的用户界面对象当然是窗口了。窗口对象有它自己的线程亲缘性。创建窗口的线程是与窗口具有不可分割关系的线程。非正式地说,线程”拥有”它所创建的窗口。消息仅在拥有它的线程上调度到窗口过程,一般来说,对窗口的修改应仅从拥有它的线程进行。
尽管窗口管理器允许任何线程访问窗口属性、样式和其他属性(如窗口过程),并且从窗口管理器的角度来看,此类访问是线程安全的,但加载-修改-写入序列通常应限制为窗口的所有者线程。否则,你会遇到如下图所示的竞争条件。
>> 请移步至 topomel.com 以查看图片 <<
如果从任何线程不小心修改窗口过程,则在前两行之间,第二个线程可能会更改窗口的窗口过程,从而导致 newWndProc 将错误的”上一个”窗口过程传递给 CallWindowProc。
那么,为什么 Windows 甚至允许非所有者线程首先更改窗口过程呢?
因为,众所周知,16 位 Windows 是一个协作式多任务系统,这意味着一个线程可以安全地做任何它想做的事情,因为知道没有其他线程会中断它,直到它明确放弃对CPU的控制。因此,上述代码在 16 位 Windows 中是安全的。出于兼容性原因,代码仍然是合法的,即使它不再安全。
(但是请注意,为了限制安全风险的影响范围,窗口管理器只允许拥有窗口的进程中的线程更改窗口过程。这是一个合理的限制,因为单独的地址空间意味着其他进程中的函数地址在拥有窗口的进程中毫无意义。)
总结
在 拓扑梅尔智慧办公平台 (Topomel Box) 的开发中,我始终坚守的一个编码规则是:仅在主线程中进行用户界面有关的修改,决不能在工作线程中做这种事,所有的修改要求都通过 Windows 消息来通知主线程。
对我而言,这是一个很有用的规则,一是它是简洁而不用思索的(所有的情况都直接套用这一规则,不必纠结),而是这篇文章中所提到的线程亲缘性规则。
最后
Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:《Thread affinity of user interface objects, part 1: Window handles》