• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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