]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | <?xml version="1.0" encoding="utf-8"?> |
2 | <!DOCTYPE section PUBLIC "-//Boost//DTD BoostBook XML V1.0//EN" | |
3 | "http://www.boost.org/tools/boostbook/dtd/boostbook.dtd"> | |
4 | <!-- | |
5 | Copyright Frank Mori Hess 2009 | |
6 | ||
7 | Distributed under the Boost Software License, Version 1.0. (See accompanying | |
8 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
9 | --> | |
10 | <section last-revision="$Date: 2007-06-12 14:01:23 -0400 (Tue, 12 Jun 2007) $" id="signals2.thread-safety"> | |
11 | <title>Thread-Safety</title> | |
12 | ||
13 | <using-namespace name="boost::signals2"/> | |
14 | <using-namespace name="boost"/> | |
15 | ||
16 | <section> | |
17 | <title>Introduction</title> | |
18 | <para> | |
19 | The primary motivation for Boost.Signals2 is to provide a version of | |
20 | the original Boost.Signals library which can be used safely in a | |
21 | multi-threaded environment. | |
22 | This is achieved primarily through two changes from the original Boost.Signals | |
23 | API. One is the introduction of a new automatic connection management scheme | |
24 | relying on <classname>shared_ptr</classname> and <classname>weak_ptr</classname>, | |
25 | as described in the <link linkend="signals2.tutorial.connection-management">tutorial</link>. | |
26 | The second change was the introduction of a <code>Mutex</code> template type | |
27 | parameter to the <classname alt="signals2::signal">signal</classname> class. This section details how | |
28 | the library employs these changes to provide thread-safety, and | |
29 | the limits of the provided thread-safety. | |
30 | </para> | |
31 | </section> | |
32 | <section> | |
33 | <title>Signals and combiners</title> | |
34 | <para> | |
35 | Each signal object default-constructs a <code>Mutex</code> object to protect | |
36 | its internal state. Furthermore, a <code>Mutex</code> is created | |
37 | each time a new slot is connected to the signal, to protect the | |
38 | associated signal-slot connection. | |
39 | </para> | |
40 | <para> | |
41 | A signal's mutex is automatically locked whenever any of the | |
42 | signal's methods are called. The mutex is usually held until the | |
43 | method completes, however there is one major exception to this rule. When | |
44 | a signal is invoked by calling | |
45 | <methodname alt="signal::operator()">signal::operator()</methodname>, | |
46 | the invocation first acquires a lock on the signal's mutex. Then | |
47 | it obtains a handle to the signal's slot list and combiner. Next | |
48 | it releases the signal's mutex, before invoking the combiner to | |
49 | iterate through the slot list. Thus no mutexes are held by the | |
50 | signal while a slot is executing. This design choice | |
51 | makes it impossible for user code running in a slot | |
52 | to deadlock against any of the | |
53 | mutexes used internally by the Boost.Signals2 library. | |
54 | It also prevents slots from accidentally causing | |
55 | recursive locking attempts on any of the library's internal mutexes. | |
56 | Therefore, if you invoke a signal concurrently from multiple threads, | |
57 | it is possible for the signal's combiner to be invoked concurrently | |
58 | and thus the slots to execute concurrently. | |
59 | </para> | |
60 | <para> | |
61 | During a combiner invocation, the following steps are performed in order to | |
62 | find the next callable slot while iterating through the signal's | |
63 | slot list. | |
64 | </para> | |
65 | <itemizedlist> | |
66 | <listitem> | |
67 | <para>The <code>Mutex</code> associated with the connection to the | |
68 | slot is locked.</para> | |
69 | </listitem> | |
70 | <listitem> | |
71 | <para>All the tracked <classname>weak_ptr</classname> associated with the | |
72 | slot are copied into temporary <classname>shared_ptr</classname> which | |
73 | will be kept alive until the invocation is done with the slot. If this fails due | |
74 | to any of the | |
75 | <classname>weak_ptr</classname> being expired, the connection is | |
76 | automatically disconnected. Therefore a slot will never be run | |
77 | if any of its tracked <classname>weak_ptr</classname> have expired, | |
78 | and none of its tracked <classname>weak_ptr</classname> will | |
79 | expire while the slot is running. | |
80 | </para> | |
81 | </listitem> | |
82 | <listitem> | |
83 | <para> | |
84 | The slot's connection is checked to see if it is blocked | |
85 | or disconnected, and then the connection's mutex is unlocked. If the connection | |
86 | was either blocked or disconnected, we | |
87 | start again from the beginning with the next slot in the slot list. | |
88 | Otherwise, we commit to executing the slot when the combiner next | |
89 | dereferences the slot call iterator (unless the combiner should increment | |
90 | the iterator without ever dereferencing it). | |
91 | </para> | |
92 | </listitem> | |
93 | </itemizedlist> | |
94 | <para> | |
95 | Note that since we unlock the connection's mutex before executing | |
96 | its associated slot, it is possible a slot will still be executing | |
97 | after it has been disconnected by a | |
98 | <code><methodname>connection::disconnect</methodname>()</code>, if | |
99 | the disconnect was called concurrently with signal invocation. | |
100 | </para> | |
101 | <para> | |
102 | You may have noticed above that during signal invocation, the invocation only | |
103 | obtains handles to the signal's slot list and combiner while holding the | |
104 | signal's mutex. Thus concurrent signal invocations may still wind up | |
105 | accessing the | |
106 | same slot list and combiner concurrently. So what happens if the slot list is modified, | |
107 | for example by connecting a new slot, while a signal | |
108 | invocation is in progress concurrently? If the slot list is already in use, | |
109 | the signal performs a deep copy of the slot list before modifying it. | |
110 | Thus the a concurrent signal invocation will continue to use the old unmodified slot list, | |
111 | undisturbed by modifications made to the newly created deep copy of the slot list. | |
112 | Future signal invocations will receive a handle to the newly created deep | |
113 | copy of the slot list, and the old slot list will be destroyed once it | |
114 | is no longer in use. Similarly, if you change a signal's combiner with | |
115 | <methodname alt="signal::set_combiner">signal::set_combiner</methodname> | |
116 | while a signal invocation is running concurrently, the concurrent | |
117 | signal invocation will continue to use the old combiner undisturbed, | |
118 | while future signal invocations will receive a handle to the new combiner. | |
119 | </para> | |
120 | <para> | |
121 | The fact that concurrent signal invocations use the same combiner object | |
122 | means you need to insure any custom combiner you write is thread-safe. | |
123 | So if your combiner maintains state which is modified when the combiner | |
124 | is invoked, you | |
125 | may need to protect that state with a mutex. Be aware, if you hold | |
126 | a mutex in your combiner while dereferencing slot call iterators, | |
127 | you run the risk of deadlocks and recursive locking if any of | |
128 | the slots cause additional mutex locking to occur. One way to avoid | |
129 | these perils is for your combiner to release any locks before | |
130 | dereferencing a slot call iterator. The combiner classes provided by | |
131 | the Boost.Signals2 library are all thread-safe, since they do not maintain | |
132 | any state across invocations. | |
133 | </para> | |
134 | <para> | |
135 | Suppose a user writes a slot which connects another slot to the invoking signal. | |
136 | Will the newly connected slot be run during the same signal invocation in | |
137 | which the new connection was made? The answer is no. Connecting a new slot | |
138 | modifies the signal's slot list, and as explained above, a signal invocation | |
139 | already in progress will not see any modifications made to the slot list. | |
140 | </para> | |
141 | <para> | |
142 | Suppose a user writes a slot which disconnects another slot from the invoking signal. | |
143 | Will the disconnected slot be prevented from running during the same signal invocation, | |
144 | if it appears later in the slot list than the slot which disconnected it? | |
145 | This time the answer is yes. Even if the disconnected slot is still | |
146 | present in the signal's slot list, each slot is checked to see if it is | |
147 | disconnected or blocked immediately before it is executed (or not executed as | |
148 | the case may be), as was described in more detail above. | |
149 | </para> | |
150 | </section> | |
151 | <section> | |
152 | <title>Connections and other classes</title> | |
153 | <para> | |
154 | The methods of the <classname>signals2::connection</classname> class are thread-safe, | |
155 | with the exception of assignment and swap. This is achived via locking the mutex | |
156 | associated with the object's underlying signal-slot connection. Assignment and | |
157 | swap are not thread-safe because the mutex protects the underlying connection | |
158 | which a <classname>signals2::connection</classname> object references, not | |
159 | the <classname>signals2::connection</classname> object itself. That is, | |
160 | there may be many copies of a <classname>signals2::connection</classname> object, | |
161 | all of which reference the same underlying connection. There is not a mutex | |
162 | for each <classname>signals2::connection</classname> object, there is only | |
163 | a single mutex protecting the underlying connection they reference. | |
164 | </para> | |
165 | <para>The <classname>shared_connection_block</classname> class obtains some thread-safety | |
166 | from the <code>Mutex</code> protecting the underlying connection which is blocked | |
167 | and unblocked. The internal reference counting which is used to keep track of | |
168 | how many <classname>shared_connection_block</classname> objects are asserting | |
169 | blocks on their underlying connection is also thread-safe (the implementation | |
170 | relies on <classname>shared_ptr</classname> for the reference counting). | |
171 | However, individual <classname>shared_connection_block</classname> objects | |
172 | should not be accessed concurrently by multiple threads. As long as two | |
173 | threads each have their own <classname>shared_connection_block</classname> object, | |
174 | then they may use them in safety, even if both <classname>shared_connection_block</classname> | |
175 | objects are copies and refer to the same underlying connection. | |
176 | </para> | |
177 | <para> | |
178 | The <classname>signals2::slot</classname> class has no internal mutex locking | |
179 | built into it. It is expected that slot objects will be created then | |
180 | connected to a signal in a single thread. Once they have been copied into | |
181 | a signal's slot list, they are protected by the mutex associated with | |
182 | each signal-slot connection. | |
183 | </para> | |
184 | <para>The <classname>signals2::trackable</classname> class does NOT provide | |
185 | thread-safe automatic connection management. In particular, it leaves open the | |
186 | possibility of a signal invocation calling into a partially destructed object | |
187 | if the trackable-derived object is destroyed in a different thread from the | |
188 | one invoking the signal. | |
189 | <classname>signals2::trackable</classname> is only provided as a convenience | |
190 | for porting single-threaded code from Boost.Signals to Boost.Signals2. | |
191 | </para> | |
192 | </section> | |
193 | </section> |