Threadsafe signals in C++
Dmitry Koplyarov, 2016
Some remarks about the code in these slides
● The namespace names are omitted
● Sometimes the const references are omitted
● The example code has poor design for simplicity purposes
● The example classes only have methods that relate to signals
● TypePtr is a typedef for shared_ptr<Type>
boost::signals2
Declaring a boost signal
Connecting to a boost signal
Invoking a boost signal
What about the multithreaded use of
boost::signals2?
Two major problems
1. No natural way to connect to a signal and get the object’s state without a race
condition
2. The “disconnect” method does not guarantee that the execution flow is out of the
signal handler right after disconnecting
Two major problems
1. No natural way to connect to a signal and get the object’s state without a race
condition
2. The “disconnect” method does not guarantee that the execution flow is out of the
signal handler right after disconnecting
The Problem
Let’s implement a StorageManager class that manages removable storages (USB sticks,
DVD disks, etc.)
The Problem
There’s a race condition in the client code
The Problem
There’s a race condition in the client code
Trying to solve it
Apparently, we need a mutex
Trying to solve it
Connecting to a signal
Trying to solve it
Invoking the signal
Drawbacks
● A redundant interface with many degrees of freedom
● The StorageManager object is not actually thread-safe
● Mostly copy-pasted code to connect to any similar signal
Two major problems
1. No natural way to connect to a signal and get the object’s state without a race
condition
2. The “disconnect” method does not guarantee that the execution flow is out of the
signal handler right after disconnecting
The Problem
Let’s implement a simple MediaScanner class
The Problem
Trying to solve it
Boost suggests using slot_type::track() method, but we need a shared_ptr to the
tracked object
Trying to solve it
Client code
Trying to solve it
...and here’s the populating of the “already there” storages
WAT?
● Too much code for nothing but creating of a MediaScanner object
● The interaction between MediaScanner and StorageManager objects spread
outside of the MediaScanner class
● Client code is forced to own the MediaScanner object via shared_ptr
● Again, all this code doesn’t make much sense, and is almost the same for all
similar cases
Make things easier for the client code
OK, let’s hide all the details inside the MediaScanner class
Make things easier for the client code
Looks better
Client code
Drawbacks
● Requires an internal Impl class
● Still a lot of “almost copy-pasted” code to connect to a signal
Trying to get rid of the Impl class
Well, there is enable_shared_from_this template in boost
Trying to get rid of the Impl class
But it does not work in the constructor, so we need a separate public method
Trying to get rid of the Impl class
Client code
Drawbacks
● Client code is forced to own the MediaScanner object via shared_ptr
● Client code is responsible for invoking MediaScanner::ConnectToSignals
● Still a lot of “almost copy-pasted” code to connect to a signal
Factory method
Factory method
Factory method
Client code
Drawbacks
● Client code is forced to own the MediaScanner object via shared_ptr
● Still a lot of “almost copy-pasted” code to connect to a signal
Designing better signals
Designing better signals
Problem:
No natural way to connect to a signal and get the object’s state without a race
condition
Solution:
The mutex and the collection were pretty good, but all that code might be hidden
inside the “connect” method
Designing better signals
Problem:
No natural way to connect to a signal and get the object’s state without a race
condition
Solution:
The mutex and the collection were pretty good, but all that code might be hidden
inside the “connect” method
Also:
The way we obtain the object state depends on the state nature. It is different for a
collection state and for a single object state. Thus, we need an abstraction here
The Populator
The Populator
How to use
How to use
The signal constructor and the populator
How to use
You may use a lambda function as a populator instead of a separate method
How to use
Or even a lambda function with an auto argument type if you use C++14
How to use
Invoking signal
How to use
Client code
Problem:
The “disconnect” method does not guarantee that the execution flow is out of the
signal handler right after disconnecting
Solution:
We need a way to mark the handler as “dying” and wait for the end of its
execution if necessary
Designing better signals
Problem:
The “disconnect” method does not guarantee that the execution flow is out of the
signal handler right after disconnecting
Solution:
We need a way to mark the handler as “dying” and wait for the end of its
execution if necessary
Also:
The SignalConnection destructor should wait on some object, and the handler
should lock that object for the execution time
Designing better signals
The LifeToken
LifeToken
an object that controls the handler lifetime, and has a blocking Release method
LifeToken::Checker
stored inside the handler info and references the LifeToken
LifeToken::Checker::ExecutionGuard
a local object that locks the LifeToken for the handler execution time
The LifeToken
The LifeToken
The LifeToken
The LifeToken
The LifeToken
What is inside the LifeToken?
● Some OS primitive for waiting (I use Mutex in these slides)
● Alive flag
● Lock counter (omitted here to make the code simpler)
● ExecutionGuard constructor should block the OS primitive
● ExecutionGuard destructor should unblock the OS primitive
● LifeToken destructor should wait until the OS primitive is unblocked
What is inside the LifeToken?
What is inside the LifeToken?
What is inside the LifeToken?
What is inside the LifeToken?
What is inside the LifeToken?
So, what does MediaScanner with these
signals look like?
New MediaScanner
New MediaScanner
New MediaScanner
Client code
Summary
There are two key features that result in much clearer client code:
1. Populators send the object state via the same handler that is used for further
updates tracking
2. A blocking disconnect method results in better incapsulation, without a need for a
nested Impl class or factory methods
Is there a ready-to-use solution?
The wigwag library
● A header-only library
● Fast and lightweight signals implementation
● Template policies for threading, exception handling and everything
● Async handlers
● Listeners
https://github.com/koplyarov/wigwag
Wigwag vs boost comparison
CPU:
Intel Core i7-3517U @ 1.90GHz
Operating system:
Ubuntu 15.10
https://github.com/koplyarov/wigwag
Wigwag vs boost comparison
ui_signal:
The most lightweight wigwag signal version. No threading, no populators, no
handler life control. Great for UI components.
signal:
A default wigwag signal configuration. Suits most developers.
boost:
Signals from boost::signals2 library. Both tracking and non-tracking slot versions
are used in handler comparison.
https://github.com/koplyarov/wigwag
Signals comparison
https://github.com/koplyarov/wigwag
Signal handlers comparison
https://github.com/koplyarov/wigwag
https://github.com/koplyarov/wigwag
Questions?
Thank you!

Дмитрий Копляров , Потокобезопасные сигналы в C++

  • 1.
    Threadsafe signals inC++ Dmitry Koplyarov, 2016
  • 2.
    Some remarks aboutthe code in these slides ● The namespace names are omitted ● Sometimes the const references are omitted ● The example code has poor design for simplicity purposes ● The example classes only have methods that relate to signals ● TypePtr is a typedef for shared_ptr<Type>
  • 3.
  • 4.
  • 5.
    Connecting to aboost signal
  • 6.
  • 7.
    What about themultithreaded use of boost::signals2?
  • 8.
    Two major problems 1.No natural way to connect to a signal and get the object’s state without a race condition 2. The “disconnect” method does not guarantee that the execution flow is out of the signal handler right after disconnecting
  • 9.
    Two major problems 1.No natural way to connect to a signal and get the object’s state without a race condition 2. The “disconnect” method does not guarantee that the execution flow is out of the signal handler right after disconnecting
  • 10.
    The Problem Let’s implementa StorageManager class that manages removable storages (USB sticks, DVD disks, etc.)
  • 11.
    The Problem There’s arace condition in the client code
  • 12.
    The Problem There’s arace condition in the client code
  • 13.
    Trying to solveit Apparently, we need a mutex
  • 14.
    Trying to solveit Connecting to a signal
  • 15.
    Trying to solveit Invoking the signal
  • 16.
    Drawbacks ● A redundantinterface with many degrees of freedom ● The StorageManager object is not actually thread-safe ● Mostly copy-pasted code to connect to any similar signal
  • 17.
    Two major problems 1.No natural way to connect to a signal and get the object’s state without a race condition 2. The “disconnect” method does not guarantee that the execution flow is out of the signal handler right after disconnecting
  • 18.
    The Problem Let’s implementa simple MediaScanner class
  • 19.
  • 20.
    Trying to solveit Boost suggests using slot_type::track() method, but we need a shared_ptr to the tracked object
  • 21.
    Trying to solveit Client code
  • 22.
    Trying to solveit ...and here’s the populating of the “already there” storages
  • 23.
    WAT? ● Too muchcode for nothing but creating of a MediaScanner object ● The interaction between MediaScanner and StorageManager objects spread outside of the MediaScanner class ● Client code is forced to own the MediaScanner object via shared_ptr ● Again, all this code doesn’t make much sense, and is almost the same for all similar cases
  • 24.
    Make things easierfor the client code OK, let’s hide all the details inside the MediaScanner class
  • 25.
    Make things easierfor the client code
  • 26.
  • 27.
    Drawbacks ● Requires aninternal Impl class ● Still a lot of “almost copy-pasted” code to connect to a signal
  • 28.
    Trying to getrid of the Impl class Well, there is enable_shared_from_this template in boost
  • 29.
    Trying to getrid of the Impl class But it does not work in the constructor, so we need a separate public method
  • 30.
    Trying to getrid of the Impl class Client code
  • 31.
    Drawbacks ● Client codeis forced to own the MediaScanner object via shared_ptr ● Client code is responsible for invoking MediaScanner::ConnectToSignals ● Still a lot of “almost copy-pasted” code to connect to a signal
  • 32.
  • 33.
  • 34.
  • 35.
    Drawbacks ● Client codeis forced to own the MediaScanner object via shared_ptr ● Still a lot of “almost copy-pasted” code to connect to a signal
  • 36.
  • 37.
    Designing better signals Problem: Nonatural way to connect to a signal and get the object’s state without a race condition Solution: The mutex and the collection were pretty good, but all that code might be hidden inside the “connect” method
  • 38.
    Designing better signals Problem: Nonatural way to connect to a signal and get the object’s state without a race condition Solution: The mutex and the collection were pretty good, but all that code might be hidden inside the “connect” method Also: The way we obtain the object state depends on the state nature. It is different for a collection state and for a single object state. Thus, we need an abstraction here
  • 39.
  • 40.
  • 41.
  • 42.
    How to use Thesignal constructor and the populator
  • 43.
    How to use Youmay use a lambda function as a populator instead of a separate method
  • 44.
    How to use Oreven a lambda function with an auto argument type if you use C++14
  • 45.
  • 46.
  • 47.
    Problem: The “disconnect” methoddoes not guarantee that the execution flow is out of the signal handler right after disconnecting Solution: We need a way to mark the handler as “dying” and wait for the end of its execution if necessary Designing better signals
  • 48.
    Problem: The “disconnect” methoddoes not guarantee that the execution flow is out of the signal handler right after disconnecting Solution: We need a way to mark the handler as “dying” and wait for the end of its execution if necessary Also: The SignalConnection destructor should wait on some object, and the handler should lock that object for the execution time Designing better signals
  • 49.
    The LifeToken LifeToken an objectthat controls the handler lifetime, and has a blocking Release method LifeToken::Checker stored inside the handler info and references the LifeToken LifeToken::Checker::ExecutionGuard a local object that locks the LifeToken for the handler execution time
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
    What is insidethe LifeToken? ● Some OS primitive for waiting (I use Mutex in these slides) ● Alive flag ● Lock counter (omitted here to make the code simpler) ● ExecutionGuard constructor should block the OS primitive ● ExecutionGuard destructor should unblock the OS primitive ● LifeToken destructor should wait until the OS primitive is unblocked
  • 56.
    What is insidethe LifeToken?
  • 57.
    What is insidethe LifeToken?
  • 58.
    What is insidethe LifeToken?
  • 59.
    What is insidethe LifeToken?
  • 60.
    What is insidethe LifeToken?
  • 61.
    So, what doesMediaScanner with these signals look like?
  • 62.
  • 63.
  • 64.
  • 65.
    Summary There are twokey features that result in much clearer client code: 1. Populators send the object state via the same handler that is used for further updates tracking 2. A blocking disconnect method results in better incapsulation, without a need for a nested Impl class or factory methods
  • 66.
    Is there aready-to-use solution?
  • 67.
    The wigwag library ●A header-only library ● Fast and lightweight signals implementation ● Template policies for threading, exception handling and everything ● Async handlers ● Listeners https://github.com/koplyarov/wigwag
  • 68.
    Wigwag vs boostcomparison CPU: Intel Core i7-3517U @ 1.90GHz Operating system: Ubuntu 15.10 https://github.com/koplyarov/wigwag
  • 69.
    Wigwag vs boostcomparison ui_signal: The most lightweight wigwag signal version. No threading, no populators, no handler life control. Great for UI components. signal: A default wigwag signal configuration. Suits most developers. boost: Signals from boost::signals2 library. Both tracking and non-tracking slot versions are used in handler comparison. https://github.com/koplyarov/wigwag
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.