thread_safety.xml 11 KB

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