10.4 多线程扩展
既然我们已经讨论了与多线程有关的一系列议题,你现在大概已经开始认同语言对多线程操作提供内建支持是很有用的。事实上,有几门语言确实提供了多线程构造。C++(www.cppentry.com)的传统则是偏好提供新的库设施而非新语言元素。我们接下来考察一些领域,看看这些领域如何提供语言级别的多线程支持,以及我们如何使用库(外加一点预处理技巧)进行实现。
10.4.1 synchronized
D和Java中都有synchronized关键字,它可以被用于守护临界区,像这样:
- Object obj = new Object();
- . . .
- synchronized(obj)
- {
- . . . // 临界区代码
- }
将synchronized关键字合并到语言中的途径之一,是将如上的代码自动转换为下面的这种形式: - { __lock_scope__<Object> __lock__(obj);
- {
- . . . // 临界区代码
- }
- }
__lock_scope__从任何意义上说都跟6.2节的lock_scope模板类似。这实现起来相当简单,并且,使用一个关联的std::lock_traits模板,可以令任何"可trait"类型的实例通过这种方式进行同步,而并不一定要将它们转换成同步对象锁。
然而,这个理由还不足以说明语言扩展的必要性,因为通过一点点宏我们就可以实现同样的效果。基本上,我们所需要的仅仅是下面这两个宏:
- #define SYNCHRONIZED_BEGIN(T, v) \
- { \
- lock_scope<T> __lock__(v);
- #define SYNCHRONIZED_END() \
- }
这种做法只有一个小小的遗憾,那就是对象的类型不能被自动推导出来,我们必须手工给出,而且代码看起来有点不够优美:- SYNCHRONIZED_BEGIN(Object, obj)
- {
- . . . // 临界区代码
- }
- SYNCHRONIZED_END()
如果你不喜欢SYNCHRONIZED_END()部分,你可以耍一点小技巧,这样来定义SYNCHRONIZED()宏: - #define SYNCHRONIZED(T, v) \
- for(synchronized_lock<lock_scope<T> > __lock__(v); \
- __lock__; __lock__.end_loop())
类模板synchronized_lock<>在这里只是用于定义一个状态,2并结束这个for循环,因为我们不能在这个for循环内声明第二个条件变量(见17.3节)。这是一个螺栓(bolt-in)类(见第22章),大致如下:
程序清单10.5
- template <typename T>
- struct synchronized_lock
- : public T
- {
- public:
- template <typename U>
- synchronized_lock(U &u)
- : T(u)
- , m_bEnded(false)
- {}
- operator bool () const
- {
- return !m_bEnded;
- }
- void end_loop()
- {
- m_bEnded = true;
- }
- private:
- bool m_bEnded;
- };
这里还有另一个问题(当然了!)。正如17.3节描述的,编译器对for循环的声明具有不同的反应。如果同一个作用域中出现了两个同步区段,那么有些老式的编译器就会发出抱怨。- SYNCHRONIZED(Object, obj)
- {
- . . . // 临界区代码
- }
- . . . // 非临界区代码
- SYNCHRONIZED(Object, obj) // 错误:“重新定义__lock__”
- {
- . . . // 其他临界区代码
- }
因此,一个可移植的解决方案必须确保每个"__lock__"都是不一样的,为此我们不得不面对"肮脏的"预处理代码:- #define concat__(x, y) x ## y
- #define concat_(x, y) concat__(x, y)
- #define SYNCHRONIZED(T, v) \
- for(synchronized_lock<lock_scope<T> > \
- concat_(__lock__, __LINE__) (v); \
- concat_(__lock__, __LINE__); \
- concat_(__lock__, __LINE__) .end_loop())
很丑陋,不是吗?但这种方案在我测试过的所有编译器上都能工作。如果你不需要考虑有关for循环的历史遗留错误,你可以使用原先那个更简洁的版本。随书光盘中包含了这些宏和类的完整版本。