ff01014782
[SVN r53569]
194 lines
11 KiB
XML
194 lines
11 KiB
XML
<?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>
|