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