简体   繁体   中英

C++14 thread/condition variable misunderstanding

I'm trying to run a thread with a function from a class member and use conditional variable to wait until the main thread signals and add the times the thread got signaled. Here is the code:

// Example program
#include <iostream>
#include <string>
#include <atomic>
#include <thread>
#include <unistd.h>
#include <mutex>
#include <condition_variable>

std::mutex m_mutex;
std::condition_variable m_condVar;
char stop =0;

class dummclass
{
    std::thread dummclass_thread;
    int alarms;

public:
    dummclass() : 
        alarms(0),
        dummclass_thread(std::thread(&dummclass::dummclassThreadProc, this))
    {
    }

    ~dummclass()
    {
        std::cout<<"Alarms: "<<alarms<<"\n";
        //signal thread before joining
        {
            std::lock_guard<std::mutex> lock_guard(m_mutex);
            stop=1;
        }
        m_condVar.notify_one();
        dummclass_thread.join();
    }
private:
    void dummclassThreadProc()
    {
          {
            std::unique_lock<std::mutex> mlock(m_mutex);
            std::cout<<"thread waiting\n";
            m_condVar.wait(mlock);
            std::cout<<"thread done waiting\n";
          }

        sleep(1);

        std::unique_lock<std::mutex> mlock(m_mutex);
        while (!stop)//!stop_dummclass.load())
        {

            std::cout<<"got mutex\n";
            m_condVar.wait(mlock);
            std::cout<<"wait done\n";

            {
                std::cout<<"got one\n";
                alarms++;
            }
        }
        std::cout<<"end loop\n";
    }
};

int main()
{
  dummclass *x = new dummclass;
  sleep(3);

  {
        std::lock_guard<std::mutex> lock_guard(m_mutex);
  }
  m_condVar.notify_one();

  std::cout<<"done waiting\n";
  sleep(3);

  for(int i=0;i<13;i++)
  {
       {
        std::cout<<"signal "<<i<<"\n";
        std::lock_guard<std::mutex> lock_guard(m_mutex);
       }
        m_condVar.notify_one();
  }
  delete x;
}

The weird part is that the initial waiting and signaling that are outside of the loops actually work ok. I don't understand what mistake I do so that the while loop inside the class thread doesn't catch any signal from the main thread but it catches a signal from the destructor of the dummyclass when I delete it. This is the output:

thread waiting
done waiting
thread done waiting
got mutex
signal 0 signal 1 signal 2 signal 3 signal 4 signal 5 signal 6 signal 7 signal 8 signal 9 signal 10 signal 11 signal 12
Alarms: 0
wait done
got one end loop

EDIT: It seems that adding a 1 second sleep in the main() for loop solves the problem. Is it possible that the for loop gets the mutex before wait() manages to wake and lock the mutex ?

for(int i=0;i<13;i++)
  {
       {std::cout<<"signal "<<i<<"\n";
        std::lock_guard<std::mutex> lock_guard(m_mutex);}
        m_condVar.notify_one();
        sleep(1);
  }

Can someone please show me what is wrong ?

Thanks.

The object doing the waiting gets delete d before it processes the signal. Since the delete happens on a known to be running thread it has a fair chance to get executed first. In particular it is also likely to reacquire the lock again: Since the notify_one() is done while the mutex is locked the wait() ing thread cannot acquire it and will go back to sleep, waiting for the mutex to be released. That gives the signalling thread an opportunity to reacquire the lock. The only forced synchronizqtion causing the signalling thread to wait is the join() and it does give the waiting thread a chance to execute.

Note that signals of condition variables are not something delivered to the waiting thread. They are essentially wake-up calls. The waiting thread will wake up eventually once a signal is delivered. However, many signals can be delivered before it actually does so.

I don't understand what mistake I do so that the while loop inside the class thread doesn't catch any signal from the main thread

Even though multiple notifications are sent the thread may only receive a single notification.

The notify_one() call does

  • not mean that the current thread will stop and wait for another thread.
  • It just means that the other thread must wake up at some point because something may have happened that it would be interested in.

Also note that std::condition_variable::wait could experience a spurious wakeup , so it might not even have anything to do or have received a 'real' signal.

The solution is to provide a predicate as a parameter to the wait() call. The predicate can then check if there is a signal (via a variable provided for this purpose and only changed under lock) and may also check if the program has been stopped.

In the updated program below I've added a predicate to the wait and made some minor changes. The program only notifies under lock, but you might choose not to .

// Example program - modified
#include <iostream>
#include <string>
#include <atomic>
#include <thread>
//#include <unistd.h>
#include <mutex>
#include <condition_variable>
#include <chrono>

std::mutex m_mutex;
std::condition_variable m_condVar;
bool signal_waiting{false};
bool stop{false};

class dummclass
{
    int alarms{};
    std::thread dummclass_thread{[this](){dummclassThreadProc(); }};

public:
    ~dummclass()
    {
        std::cout << "Alarms: " << alarms << "\n";
        //signal thread before joining
        {
            std::lock_guard<std::mutex> lock_guard(m_mutex);
            stop = 1;
            m_condVar.notify_one();
        }
        dummclass_thread.join();
    }
private:
    void dummclassThreadProc()
    {
        {
            std::unique_lock<std::mutex> mlock(m_mutex);
            std::cout << "thread waiting\n";
            m_condVar.wait(mlock);
            std::cout << "thread done waiting\n";
        }

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

        while(!stop)//!stop_dummclass.load())
        {
            std::unique_lock<std::mutex> mlock(m_mutex);
            std::cout << "got mutex\n";
            //m_condVar.wait(mlock);
            m_condVar.wait(mlock, [](){return signal_waiting || stop; });
            if(stop)
                break;
            std::cout << "wait done\n";

            std::cout << "got one\n";
            alarms++;
            signal_waiting = false;
            m_condVar.notify_one();
        }
        std::cout << "end loop\n";
    }
};

int main()
{
    dummclass *x = new dummclass;
    //sleep(3);
    std::this_thread::sleep_for(std::chrono::seconds{1});

    {
        std::lock_guard<std::mutex> lock_guard(m_mutex);
        m_condVar.notify_one();
    }

    std::cout << "done waiting\n";
    //sleep(3);
    std::this_thread::sleep_for(std::chrono::seconds{1});

    for(int i = 0; i<13; i++)
    {
        {
            std::cout << "signal " << i << "\n";
            std::unique_lock<std::mutex> lock(m_mutex);
            m_condVar.wait(lock, [](){return !signal_waiting ; });
            signal_waiting = true;
            m_condVar.notify_one();
        }
    }
    delete x;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM