74
Threadsafe signals in C++ Dmitry Koplyarov, 2016

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

Embed Size (px)

Citation preview

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 problems1. 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 problems1. 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 ProblemLet’s implement a StorageManager class that manages removable storages (USB sticks,

DVD disks, etc.)

The ProblemThere’s a race condition in the client code

The ProblemThere’s a race condition in the client code

Trying to solve itApparently, we need a mutex

Trying to solve itConnecting to a signal

Trying to solve itInvoking 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 problems1. 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 ProblemLet’s implement a simple MediaScanner class

The Problem

Trying to solve itBoost suggests using slot_type::track() method, but we need a shared_ptr to the

tracked object

Trying to solve itClient 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 codeOK, let’s hide all the details inside the MediaScanner class

Make things easier for the client code

Looks betterClient 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 classWell, there is enable_shared_from_this template in boost

Trying to get rid of the Impl classBut it does not work in the constructor, so we need a separate public method

Trying to get rid of the Impl classClient 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 methodClient 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 signalsProblem:

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 signalsProblem:

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 useThe signal constructor and the populator

How to useYou may use a lambda function as a populator instead of a separate method

How to useOr even a lambda function with an auto argument type if you use C++14

How to useInvoking signal

How to useClient 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 LifeTokenLifeToken

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 MediaScannerClient code

SummaryThere 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 comparisonCPU:

Intel Core i7-3517U @ 1.90GHz

Operating system:

Ubuntu 15.10

https://github.com/koplyarov/wigwag

Wigwag vs boost comparisonui_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!