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
原文链接: https://www.cnblogs.com/linannk/p/5554936.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/234453
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!