123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- <?xml version="1.0" encoding="utf-8"?>
- <!DOCTYPE section PUBLIC "-//Boost//DTD BoostBook XML V1.0//EN"
- "http://www.boost.org/tools/boostbook/dtd/boostbook.dtd">
- <!--
- Copyright Frank Mori Hess 2009
- Distributed under the Boost Software License, Version 1.0. (See accompanying
- file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
- -->
- <section last-revision="$Date: 2007-06-12 14:01:23 -0400 (Tue, 12 Jun 2007) $" id="signals2.thread-safety">
- <title>Thread-Safety</title>
- <using-namespace name="boost::signals2"/>
- <using-namespace name="boost"/>
- <section>
- <title>Introduction</title>
- <para>
- The primary motivation for Boost.Signals2 is to provide a version of
- the original Boost.Signals library which can be used safely in a
- multi-threaded environment.
- This is achieved primarily through two changes from the original Boost.Signals
- API. One is the introduction of a new automatic connection management scheme
- relying on <classname>shared_ptr</classname> and <classname>weak_ptr</classname>,
- as described in the <link linkend="signals2.tutorial.connection-management">tutorial</link>.
- The second change was the introduction of a <code>Mutex</code> template type
- parameter to the <classname alt="signals2::signal">signal</classname> class. This section details how
- the library employs these changes to provide thread-safety, and
- the limits of the provided thread-safety.
- </para>
- </section>
- <section>
- <title>Signals and combiners</title>
- <para>
- Each signal object default-constructs a <code>Mutex</code> object to protect
- its internal state. Furthermore, a <code>Mutex</code> is created
- each time a new slot is connected to the signal, to protect the
- associated signal-slot connection.
- </para>
- <para>
- A signal's mutex is automatically locked whenever any of the
- signal's methods are called. The mutex is usually held until the
- method completes, however there is one major exception to this rule. When
- a signal is invoked by calling
- <methodname alt="signal::operator()">signal::operator()</methodname>,
- the invocation first acquires a lock on the signal's mutex. Then
- it obtains a handle to the signal's slot list and combiner. Next
- it releases the signal's mutex, before invoking the combiner to
- iterate through the slot list. Thus no mutexes are held by the
- signal while a slot is executing. This design choice
- makes it impossible for user code running in a slot
- to deadlock against any of the
- mutexes used internally by the Boost.Signals2 library.
- It also prevents slots from accidentally causing
- recursive locking attempts on any of the library's internal mutexes.
- Therefore, if you invoke a signal concurrently from multiple threads,
- it is possible for the signal's combiner to be invoked concurrently
- and thus the slots to execute concurrently.
- </para>
- <para>
- During a combiner invocation, the following steps are performed in order to
- find the next callable slot while iterating through the signal's
- slot list.
- </para>
- <itemizedlist>
- <listitem>
- <para>The <code>Mutex</code> associated with the connection to the
- slot is locked.</para>
- </listitem>
- <listitem>
- <para>All the tracked <classname>weak_ptr</classname> associated with the
- slot are copied into temporary <classname>shared_ptr</classname> which
- will be kept alive until the invocation is done with the slot. If this fails due
- to any of the
- <classname>weak_ptr</classname> being expired, the connection is
- automatically disconnected. Therefore a slot will never be run
- if any of its tracked <classname>weak_ptr</classname> have expired,
- and none of its tracked <classname>weak_ptr</classname> will
- expire while the slot is running.
- </para>
- </listitem>
- <listitem>
- <para>
- The slot's connection is checked to see if it is blocked
- or disconnected, and then the connection's mutex is unlocked. If the connection
- was either blocked or disconnected, we
- start again from the beginning with the next slot in the slot list.
- Otherwise, we commit to executing the slot when the combiner next
- dereferences the slot call iterator (unless the combiner should increment
- the iterator without ever dereferencing it).
- </para>
- </listitem>
- </itemizedlist>
- <para>
- Note that since we unlock the connection's mutex before executing
- its associated slot, it is possible a slot will still be executing
- after it has been disconnected by a
- <code><methodname>connection::disconnect</methodname>()</code>, if
- the disconnect was called concurrently with signal invocation.
- </para>
- <para>
- You may have noticed above that during signal invocation, the invocation only
- obtains handles to the signal's slot list and combiner while holding the
- signal's mutex. Thus concurrent signal invocations may still wind up
- accessing the
- same slot list and combiner concurrently. So what happens if the slot list is modified,
- for example by connecting a new slot, while a signal
- invocation is in progress concurrently? If the slot list is already in use,
- the signal performs a deep copy of the slot list before modifying it.
- Thus the a concurrent signal invocation will continue to use the old unmodified slot list,
- undisturbed by modifications made to the newly created deep copy of the slot list.
- Future signal invocations will receive a handle to the newly created deep
- copy of the slot list, and the old slot list will be destroyed once it
- is no longer in use. Similarly, if you change a signal's combiner with
- <methodname alt="signal::set_combiner">signal::set_combiner</methodname>
- while a signal invocation is running concurrently, the concurrent
- signal invocation will continue to use the old combiner undisturbed,
- while future signal invocations will receive a handle to the new combiner.
- </para>
- <para>
- The fact that concurrent signal invocations use the same combiner object
- means you need to insure any custom combiner you write is thread-safe.
- So if your combiner maintains state which is modified when the combiner
- is invoked, you
- may need to protect that state with a mutex. Be aware, if you hold
- a mutex in your combiner while dereferencing slot call iterators,
- you run the risk of deadlocks and recursive locking if any of
- the slots cause additional mutex locking to occur. One way to avoid
- these perils is for your combiner to release any locks before
- dereferencing a slot call iterator. The combiner classes provided by
- the Boost.Signals2 library are all thread-safe, since they do not maintain
- any state across invocations.
- </para>
- <para>
- Suppose a user writes a slot which connects another slot to the invoking signal.
- Will the newly connected slot be run during the same signal invocation in
- which the new connection was made? The answer is no. Connecting a new slot
- modifies the signal's slot list, and as explained above, a signal invocation
- already in progress will not see any modifications made to the slot list.
- </para>
- <para>
- Suppose a user writes a slot which disconnects another slot from the invoking signal.
- Will the disconnected slot be prevented from running during the same signal invocation,
- if it appears later in the slot list than the slot which disconnected it?
- This time the answer is yes. Even if the disconnected slot is still
- present in the signal's slot list, each slot is checked to see if it is
- disconnected or blocked immediately before it is executed (or not executed as
- the case may be), as was described in more detail above.
- </para>
- </section>
- <section>
- <title>Connections and other classes</title>
- <para>
- The methods of the <classname>signals2::connection</classname> class are thread-safe,
- with the exception of assignment and swap. This is achived via locking the mutex
- associated with the object's underlying signal-slot connection. Assignment and
- swap are not thread-safe because the mutex protects the underlying connection
- which a <classname>signals2::connection</classname> object references, not
- the <classname>signals2::connection</classname> object itself. That is,
- there may be many copies of a <classname>signals2::connection</classname> object,
- all of which reference the same underlying connection. There is not a mutex
- for each <classname>signals2::connection</classname> object, there is only
- a single mutex protecting the underlying connection they reference.
- </para>
- <para>The <classname>shared_connection_block</classname> class obtains some thread-safety
- from the <code>Mutex</code> protecting the underlying connection which is blocked
- and unblocked. The internal reference counting which is used to keep track of
- how many <classname>shared_connection_block</classname> objects are asserting
- blocks on their underlying connection is also thread-safe (the implementation
- relies on <classname>shared_ptr</classname> for the reference counting).
- However, individual <classname>shared_connection_block</classname> objects
- should not be accessed concurrently by multiple threads. As long as two
- threads each have their own <classname>shared_connection_block</classname> object,
- then they may use them in safety, even if both <classname>shared_connection_block</classname>
- objects are copies and refer to the same underlying connection.
- </para>
- <para>
- The <classname>signals2::slot</classname> class has no internal mutex locking
- built into it. It is expected that slot objects will be created then
- connected to a signal in a single thread. Once they have been copied into
- a signal's slot list, they are protected by the mutex associated with
- each signal-slot connection.
- </para>
- <para>The <classname>signals2::trackable</classname> class does NOT provide
- thread-safe automatic connection management. In particular, it leaves open the
- possibility of a signal invocation calling into a partially destructed object
- if the trackable-derived object is destroyed in a different thread from the
- one invoking the signal.
- <classname>signals2::trackable</classname> is only provided as a convenience
- for porting single-threaded code from Boost.Signals to Boost.Signals2.
- </para>
- </section>
- </section>
|