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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM