Home > Software design >  Can we use two different mutex when waiting on same conditional variable?
Can we use two different mutex when waiting on same conditional variable?

Time:12-18

Consider below scenario:

Thread 1

mutexLk1_
gcondVar_.wait(mutexLk1);

Thread 2

mutexLk2_
gcondVar_.wait(mutexLk2);

Thread 3

condVar_
gcondVar_.notify_all();

What I observe is that notify_all() does not wake up both the threads but just one on the two. If i were to replace mutexLk2 with mutexLk1. I get a functional code.

To reproduce the issue consider below modified example from cppref

#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>

std::condition_variable cv;
std::mutex cv_m1;
std::mutex cv_m; // This mutex is used for three purposes:
                 // 1) to synchronize accesses to i
                 // 2) to synchronize accesses to std::cerr
                 // 3) for the condition variable cv
int i = 0;

void waits1()
{
    std::unique_lock<std::mutex> lk(cv_m);
    std::cerr << "Waiting... \n";
    cv.wait(lk, []{return i == 1;});
    std::cerr << "...finished waiting. waits1 i == 1\n";
}

void waits()
{
    std::unique_lock<std::mutex> lk(cv_m1);
    std::cerr << "Waiting... \n";
    cv.wait(lk, []{return i == 1;});
    std::cerr << "...finished waiting. i == 1\n";
}

void signals()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    {
        std::lock_guard<std::mutex> lk(cv_m);
        std::cerr << "Notifying...\n";
    }
    cv.notify_all();

    std::this_thread::sleep_for(std::chrono::seconds(1));

    {
        std::lock_guard<std::mutex> lk(cv_m);
        i = 1;
        std::cerr << "Notifying again...\n";
    }
    cv.notify_all();
}

int main()
{
    std::thread t1(waits), t2(waits1), t3(waits), t4(signals);
    t1.join();
    t2.join();
    t3.join();
    t4.join();
}

Compilation command

g --std=c 17 t.cpp -lpthread

Interesting thing here is that the above code gets stuck on either of the waits (sometimes waits1 runs sometime waits) on a centos 7.9 System with g 9.3 version (same behavior with g 10 on this system) but with a ubuntu 18.04 system (with g 9.4) this works without any issues

Any idea what is the expected behaviour or ideal practice to follow? Because in my used case I need two different mutex to protect different data structures but the trigger is from same conditional variable.

Thanks

CodePudding user response:

It seems you violate standad:

33.5.3 Class condition_variable [thread.condition.condvar]

void wait(unique_lock& lock);

Requires: lock.owns_lock() is true and lock.mutex() is locked by the calling thread, and either

(9.1) — no other thread is waiting on this condition_variable object or

(9.2) — lock.mutex() returns the same value for each of the lock arguments supplied by all concurrently waiting (via wait, wait_for, or wait_until) threads.

Cleary two threads are waiting and lock.mutex() does not return same.

CodePudding user response:

To elaborate on my comment, there are three things that require synchronization in the question.

There are two independent hunks of data, which the question says are each properly synchronized with two independent mutexes. That's fine, but it's irrelevant.

The third synchronization issue is ensuring that some other specified condition is satisfied in order for some threads to run. That's implemented with a condition variable; so far, so good. But when more than one thread waits for a condition variable, all the waiting threads must use the same mutex. So the code needs a mutex for the threads to use when they wait. While it might be possible to use one or another of the existing mutexes for that synchronization, it seems far more likely that the code needs another mutex, specifically for use when waiting on that condition variable.

When you're dealing with synchronization, resist the urge to share things that manage synchronization; that can get you into trouble.

  • Related