Skip to content

3.7 Mutexes and condition variables

Alexander Damian edited this page Nov 13, 2018 · 2 revisions

Synchronizing access to shared objects

We have seen in previous sections that it is possible to write lock-free code by posting coroutines and IO tasks unto specific queues, thus serializing all access to shared code.

Although this solution may be sufficient for most cases, it might be necessary sometimes to modify objects which are shared between a coroutine and an IO task or between a coroutine and some other arbitrary thread outside the control of the dispatcher (e.g. the main program thread), such as those with static storage or global objects. For this specific purpose two classes have been created which will not block a coroutine (we have seen that blocking a coroutine thread can result in performance degradation) called quantum::Mutex and quantum::ConditionVariable. These two classes behave exactly as their STL counterparts and they MUST be used when synchronizing access from within a coroutine.

std::vector<int> v; //global object
    
Mutex m;
ConditionVariable cv;
    
//synchronize access to the vector from the main thread
m.lock();
    
//start a couple of coroutines waiting to access the vector
dispatcher.post([](CoroContextPtr<int> ctx,
                   Mutex& mu,
                   std::vector<int>& vec,
                   ConditionVariable& cv)->int
{
    mu.lock(ctx);
    cv.wait(ctx, mu, [&vec]()->bool{ return !vec.empty(); });
    vec.push_back(6);
    mu.unlock();
    return 0;
}, m, v, cv);

//start the 2nd coroutine
dispatcher.post([](CoroContextPtr<int> ctx,
                   Mutex& mu,
                   std::vector<int>& vec,
                   ConditionVariable& cv)->int
{
    mu.lock(ctx);
    cv.wait(ctx, mu, [&vec]()->bool{ return !vec.empty(); });
    vec.push_back(7);
    mu.unlock();
    return 0;
}, m, v, cv);
    
std::this_thread::sleep_for(std::chrono::milliseconds(200));
v.push_back(5);
m.unlock(); //release access to global object
    
cv.notifyAll();
dispatcher.drain();

//At this point 'v' will contain {5, 6, 7} or {5, 7, 6}.

When it comes to IO tasks, standard std::mutex and std::condition_variable can be used as long as the object being synchronized is not accessed by a coroutine.