C++多线程条件变量

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++多线程条件变量相关的知识,希望对你有一定的参考价值。

C++多线程中的条件变量的使用。

在多线程编程中,常常使用条件变量来等待某个事件的发生。

先看代码

  1 #include <thread>
  2 #include <mutex>
  3 #include <condition_variable>
  4 #include <list>
  5 #include <string>
  6 #include <iostream>
  7 #include <chrono>
  8 
  9 class Event {
 10 public:
 11     enum Type : int {
 12         quit = 0 ,
 13         help = 1
 14     };
 15 
 16     explicit Event(Type type)
 17         : m_type(type)
 18     { }
 19 
 20     virtual ~Event()
 21     { }
 22 
 23     Type type() const { return m_type; }
 24 private:
 25     Type m_type;
 26 };
 27 
 28 class QuitEvent
 29         : public Event
 30 {
 31 public:
 32     explicit QuitEvent(int exitCode = 0)
 33         : Event(quit)
 34         , m_exitCode(exitCode)
 35     { }
 36 
 37     void setExitCode(int exitCode) { m_exitCode = exitCode; }
 38     int exitCode() const { return m_exitCode; }
 39 
 40 private:
 41     int m_exitCode;
 42 };
 43 
 44 class HelpEvent
 45     : public Event
 46 {
 47 public:
 48     explicit HelpEvent(const std::string& msg)
 49         : Event(help)
 50         , m_msg(msg)
 51     {}
 52 
 53     void setMsg(const std::string& msg) { m_msg = msg; }
 54 
 55     std::string msg() const { return m_msg; }
 56 
 57 private:
 58     std::string m_msg;
 59 };
 60 
 61 class EventQueue {
 62 public:
 63     Event* GetEvent()
 64     {
 65         std::unique_lock<std::mutex> locker(m_evtQueueMtx);
 66         while(m_eventQueue.empty())
 67             m_evtQueueCondVar.wait(locker);
 68         Event* evt = m_eventQueue.front();
 69         m_eventQueue.pop_front();
 70         return evt;
 71     }
 72 
 73     void PushEvent(Event* evt)
 74     {
 75         m_evtQueueMtx.lock();
 76         const bool bNeedNotify = m_eventQueue.empty();
 77         m_eventQueue.push_back(evt);
 78         m_evtQueueMtx.unlock();
 79         if (bNeedNotify)
 80             m_evtQueueCondVar.notify_all();
 81     }
 82 
 83 private:
 84     std::mutex m_evtQueueMtx;
 85     std::condition_variable m_evtQueueCondVar;
 86     std::list<Event*> m_eventQueue;
 87 };
 88 
 89 void thread_proc(const std::string& name , EventQueue *queue)
 90 {
 91     for(;;)
 92     {
 93         Event *evt = queue->GetEvent();
 94         if (evt->type() == Event::quit)
 95         {
 96             QuitEvent* e = static_cast<QuitEvent*>(evt);
 97             std::cout << "thread " << name << " quit. Quit code : " << e->exitCode() << std::endl;
 98             delete e;
 99             break;
100         }
101         else if (evt->type() == Event::help)
102         {
103             HelpEvent *e = static_cast<HelpEvent*>(evt);
104             std::cout << "thread " << name << " get a help event. Msg : " << e->msg() << std::endl;
105             delete e;
106         }
107         else
108         {
109             std::cout << "thread " << name << " get an event. Type : " << evt->type() << std::endl;
110         }
111     }
112 }
113 
114 int main(int argc, char *argv[])
115 {
116     EventQueue evtQueue;
117     std::thread thread1(thread_proc , "thread1" , &evtQueue);
118     std::thread thread2(thread_proc , "thread2" , &evtQueue);
119     std::thread thread3(thread_proc , "thread3" , &evtQueue);
120     std::thread thread4(thread_proc , "thread4" , &evtQueue);
121     std::thread thread5(thread_proc , "thread5" , &evtQueue);
122     std::thread thread6(thread_proc , "thread6" , &evtQueue);
123 
124     for(int i = 0; i < 1000; ++i)
125     {
126         if (rand() % 2 == 0)
127             evtQueue.PushEvent(new Event(static_cast<Event::Type>(rand())));
128         else
129             evtQueue.PushEvent(new HelpEvent(std::to_string(rand() % 500) + "--help msg"));
130 
131         std::this_thread::sleep_for(std::chrono::milliseconds(10));
132     }
133 
134     for(int i = 0; i < 6; ++i)
135     {
136         evtQueue.PushEvent(new QuitEvent(qrand() % 500));
137     }
138 
139     thread1.join();
140     thread2.join();
141     thread3.join();
142     thread4.join();
143     thread5.join();
144     thread6.join();
145 
146     std::cout << "All Quit!" << std::endl;
147 
148     return 0;
149 }

上述代码中,有几个问题需要澄清:

1.为什么66、67行代码有一个while循环。

2.为什么条件变量的使用必须带有一个互斥锁。

3.为什么条件变量使用的互斥锁和PushEvent函数使用的互斥锁是同一个。

4.互斥锁到底保护了什么.

问题1:

  为了更加有效的使用条件变量,我们使用了condition_variable::notify_all 来切换条件变量的状态。这样所有等待的线程都有机会被唤醒。在上述例子中假如thread1先被唤醒,之后thread2被唤醒,对于thread2来说,应当再一次检查事件列队中是否有可用事件,因为thread1或者别的先于thread2被唤醒的线程可能已经将事件列队清空。所以每一次线程被唤醒都应当再次检查事件列队是有事件可用。如果没有事件则应该再次进入等待状态。

问题2:

  条件变量能够在唤醒的同时加锁。唤醒和加锁是一个原子操作,这样当线程被唤醒是就能够立即获得资源的额访问权。当访问共享资源时应当在访问前加锁,如果不满足访问条件则应该释放锁并且进入等待状态,这样别的线程才能够访问共享资源。如果条件变量不带互斥锁,则当条件变量被唤醒时,应当对共享资源加锁。则应当写一下的伪代码:

forever {

  lock

  if (ok)

  {

    access;

    unlock;

    break;

  }

  else

  {

    unlock;

    wait;

  }

}

  从上述代码看出有更多的加锁和解锁操作。当线程进入等待时会进入内核状态,多次的加锁和解锁等待会造成线程在用户态和内核态之前频繁切换,这会带来性能问题,也容易使得编写有bug的代码。

问题3:

  从对问题2的分析可以看出,两个地方使用的互斥锁是为了保护同一个资源。为了保持访问的唯一性,因此必须是同一个互斥锁。

问题4:

  到此,问题4就很简单了,互斥锁保护的是被等待的资源。上述例子中是事件列队。

 

by linannk

2016.06.03 01:02

 

以上是关于C++多线程条件变量的主要内容,如果未能解决你的问题,请参考以下文章

[C++多线程]1.3-多线程控制的另一种姿势-条件变量(condition_variable), 信号量(semaphore)

详解 C++ 多线程的condition_variable

c++多线程 唤醒notify_one/notify_all 必须发生在阻塞之前才是 有效唤醒

C ++多线程 - 条件变量以错误的方式提供数据

多线程:Signal vs BusyWait(Polling),线程间条件变量问题

C++实现 生产者消费者模型