话说Java里有个很强大的关键字叫synchronized,可以方便的实现线程同步。下面来尝试下在C++里模拟一个类似的。
Java里的synchronized有两种形式,一种是基于函数的,另种则是语块的。前者受C++的语法所限,估计是没法实现了,所以就尝试后者。
块级语法很简单:
1
2
3
|
因为Java所有变量都继承于Object,所以任意变量都能当作锁用。这在C++里无法简易实现,因此我们用特定的类型实例当作同步变量使用。
先从最经典简易的同步类说起。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
这是windows下实现线程同步最常见的封装。只需声明一个Lock实例,在需要同步的代码前后分别调用Enter和Leave即可。
既然用起来这么简单,为什么还要继续改进?显然这种方法有个很大的缺陷,如果忘了调用Leave,或者在调用之前就return/throw退出,那么就会引起死锁。
所以,我们需要类似auto_ptr的机制,自动维护栈数据的创建和删除。就暂且称它_auto_lock吧。
1
2
3
4
5
6
7
8
9
10
11
|
_auto_lock通过引用一个Lock实例来初始化,并立即锁住临界区;被销毁时则释放锁。
有了这个机制,我们再也不用担心忘了调用.Leave()。只需提供一个Lock对象,就能在当前语块自动加锁解锁。再也不用担心死锁的问题了。
1
2
3
4
5
6
7
8
9
|
进入syn code的"{"之后,_auto_lock被构造;无论用那种方式离开"}",析构函数都会被调用。
上述代码类似的在stl和boost里都是及其常见的。利用栈对象的构造/析构函数维护局部资源,算是C++很常用的一技巧。
我们的目标又近了一步。下面开始利用经典的宏定义,制造一颗synchronized语法糖,最终实现这样的语法:
1
2
3
4
5
6
7
8
9
|
显然需要一个叫synchronized宏,并且在里面定义_auto_lock。
1
|
乍一看这语法很像循环,并且要在循环内定义变量,所以用for(;;)的结构是再好不过了。
1
|
不过sync code我们只需执行一次,所以还需另一个变量来控制次数。由于for里面只能声明一种类型的变量,所以我们在外面再套一层循环:
1
|
synchronized宏将mylock替换成上述代码,既没有违反语法,也实现相同的流程。得益于循环语法,甚至可以在synchronized内使用break来跳出同步块!
我们将上述代码整理下,并做个简单的测试。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
|
使用语法糖除了好看外,有个最重要的功能就是可以在synchronized同步块里使用break来跳出,并且不会引起死锁,这是其他方法无法实现的。