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<!-- 5Copyright Frank Mori Hess 2009 6 7Distributed under the Boost Software License, Version 1.0. (See accompanying 8file 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> 194