1

I have created a program to synchronize two threads . Using mutex and condition variables.

#include <thread>
#include <string>
#include <iostream>
#include <mutex>
#include <condition_variable>
#include "signal.h"
#include <atomic>
class Class
{
public:
    Class(const std::string& s, const int& i) : m_data(s), threadName(i),counter(0) { }
    ~Class() { 
        if (m_thread.joinable())
        {
            m_thread.join();
        }
        if (m_thread2.joinable()){
            m_thread2.join();
        }

        std::cout << "destructor called" << std::endl;
        }
    void runThread() { 
        m_thread = std::thread(&Class::printThreadWrite, this); 
        m_thread2 = std::thread(&Class::printThreadRead,this);
    }

private:
    std::mutex m_printmutex;
    std::condition_variable m_conditionvar;
    std::string m_data;
    std::thread m_thread;
    std::thread m_thread2;
    std::atomic<bool> signalScheduleSwitch;
    int threadName;

    std::atomic<int> counter;
    void printThreadWrite()  { 
        
            while(1){
                {
                std::lock_guard<std::mutex> lg(m_printmutex);
                std::cout << "thread # " << std::this_thread::get_id() << " "  << m_data << '\n'; 
                counter ++;
                signalScheduleSwitch = true;
                }
                std::this_thread::sleep_for(std::chrono::milliseconds(2));
                m_conditionvar.notify_one();
            }
        
    }
    
    void printThreadRead()  { 
    
        while (1)
        {
            std::unique_lock<std::mutex> uLock(m_printmutex);
            m_conditionvar.wait(uLock,[this](){ return signalScheduleSwitch == true;});
            std::cout << "counter value is:" << counter << "\n";
            signalScheduleSwitch = false;

        }
    }
};

void signalHandler(int s){
    printf("caught signal %d Exiting\n", s);
    exit(1);

}

int main(int argc, char** argv)
{
    signal(SIGINT, signalHandler);
    try
    {
        Class c("Hello, world!",1);
        c.runThread();
    }
    catch(const std::exception& e)
    {
        std::cerr << e.what() << '\n';
    }

    return 0;
}

I am unable to get a synchronization of threads.

Using the condition variable to wait for a flag being set true, the code should switch between threads. I still don't get a clean thread scheduling. Is there any flaw in my understanding here ?

  • 2
    what does it do? how does that differ from what you expect? – 463035818_is_not_an_ai May 05 '23 at 16:31
  • 1
    This code seems to have the same problem that was answered in [your last question](https://stackoverflow.com/questions/76150930/c-thread-concurrency-inside-class). Both threads unlock your mutex and then immediately re-lock it. `std::mutex` [is not a fair mutex](https://stackoverflow.com/questions/17527621/is-stdmutex-fair). – Drew Dormann May 05 '23 at 16:33
  • Any feedback on my answer? Did you find it useful? – RandomBits May 18 '23 at 01:47
  • HI@RandomBits , yes the example you gave does work and it ensures a clean synchronization. What I am unable to comprehend is why using lock_guard does not work. Conceptually, the lock guard should automatically latch when the second thread is released and then release itself when it goes out of scope. – rebelliousconformist May 24 '23 at 11:55

1 Answers1

3

Ping-Pong with mutex and two condition variables

This is the canonical ping-pong using a mutex and condition variables. Note that 1) you need two condition variables to make ping-pong work and 2) you have to be careful about placing the output statements in a block where the lock is still held. Your code is close.

#include <iostream>
#include <condition_variable>
#include <atomic>
#include <thread>

class PingPong {
public:
    PingPong() {
        t0_ = std::thread(&PingPong::ping, this);
        t1_ = std::thread(&PingPong::pong, this);
    }

    ~PingPong() {
        if (t0_.joinable())
            t0_.join();
        if (t1_.joinable())
            t1_.join();
    }

    void ping() {

        while(counter <= 20) {
            {
                std::unique_lock<std::mutex> lck(mutex_);
                cv0_.wait(lck, [this]{ return ready_ == false; });
                ready_ = true;
                std::cout << "ping counter: " << counter << std::endl;
            }
            ++counter;
            cv1_.notify_one();
        }
    }

    void pong() {

        while(counter < 20) {
            {
                std::unique_lock<std::mutex> lck(mutex_);
                cv1_.wait(lck, [this]{ return ready_ == true; });
                ready_ = false;
                std::cout << "pong counter: " << counter << std::endl;
            }
            cv0_.notify_one();
        }
    }

private:
    bool ready_{false};
    std::mutex mutex_;
    std::condition_variable cv0_, cv1_;
    std::atomic<int> counter{};
    std::thread t0_, t1_;
};

int main(){
    PingPong p{};
}

This should result in the following output.

ping counter: 0
pong counter: 1
ping counter: 1
pong counter: 2
ping counter: 2
pong counter: 3
ping counter: 3
pong counter: 4
ping counter: 4
pong counter: 5
ping counter: 5
pong counter: 6
ping counter: 6
pong counter: 7
ping counter: 7
pong counter: 8
ping counter: 8
pong counter: 9
ping counter: 9
...

Ping-Pong with single atomic flag

Depending on your platform, it may be more performant (and a little simpler to grok) to use an atomic flag instead of condition variables. This produces the same output as above.

class PingPongAtomicFlag {
public:
    PingPongAtomicFlag() {
        t0_ = std::thread([this]() { ping(); });
        t1_ = std::thread([this]() { pong(); });
    }

    ~PingPongAtomicFlag() {
        if (t0_.joinable())
            t0_.join();
        if (t1_.joinable())
            t1_.join();
    }

    void ping() {

        while(counter_ <= 20) {
            potato_.wait(true);
            std::cout << "ping counter: " << counter_ << std::endl;
            potato_.test_and_set();
            ++counter_;
            potato_.notify_one();
        }
    }

    void pong() {

        while(counter_ < 20) {
            potato_.wait(false);
            std::cout << "pong counter: " << counter_ << std::endl;
            potato_.clear();
            potato_.notify_one();
        }
    }

private:
    std::atomic_flag potato_;
    std::atomic<int> counter_{};
    std::thread t0_, t1_;
};
RandomBits
  • 4,194
  • 1
  • 17
  • 30