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++多线程 唤醒notify_one/notify_all 必须发生在阻塞之前才是 有效唤醒