#include <thread>
#include <mutex>
#include <condition_variable>
#include <list>
#include <string>
#include <iostream>
#include <chrono>
class Event {
public:
enum Type : int {
quit = 0 ,
help = 1
};
explicit Event(Type type)
: m_type(type)
{ }
virtual ~Event()
{ }
Type type() const { return m_type; }
private:
Type m_type;
};
class QuitEvent
: public Event
{
public:
explicit QuitEvent(int exitCode = 0)
: Event(quit)
, m_exitCode(exitCode)
{ }
void setExitCode(int exitCode) { m_exitCode = exitCode; }
int exitCode() const { return m_exitCode; }
private:
int m_exitCode;
};
class HelpEvent
: public Event
{
public:
explicit HelpEvent(const std::string& msg)
: Event(help)
, m_msg(msg)
{}
void setMsg(const std::string& msg) { m_msg = msg; }
std::string msg() const { return m_msg; }
private:
std::string m_msg;
};
class EventQueue {
public:
Event* GetEvent()
{
std::unique_lock<std::mutex> locker(m_evtQueueMtx);
while(m_eventQueue.empty())
m_evtQueueCondVar.wait(locker);
Event* evt = m_eventQueue.front();
m_eventQueue.pop_front();
return evt;
}
void PushEvent(Event* evt)
{
m_evtQueueMtx.lock();
const bool bNeedNotify = m_eventQueue.empty();
m_eventQueue.push_back(evt);
m_evtQueueMtx.unlock();
if (bNeedNotify)
m_evtQueueCondVar.notify_all();
}
private:
std::mutex m_evtQueueMtx;
std::condition_variable m_evtQueueCondVar;
std::list<Event*> m_eventQueue;
};
void thread_proc(const std::string& name , EventQueue *queue)
{
for(;;)
{
Event *evt = queue->GetEvent();
if (evt->type() == Event::quit)
{
QuitEvent* e = static_cast<QuitEvent*>(evt);
std::cout << "thread " << name << " quit. Quit code : " << e->exitCode() << std::endl;
delete e;
break;
}
else if (evt->type() == Event::help)
{
HelpEvent *e = static_cast<HelpEvent*>(evt);
std::cout << "thread " << name << " get a help event. Msg : " << e->msg() << std::endl;
delete e;
}
else
{
std::cout << "thread " << name << " get an event. Type : " << evt->type() << std::endl;
}
}
}
int main(int argc, char *argv[])
{
EventQueue evtQueue;
std::thread thread1(thread_proc , "thread1" , &evtQueue);
std::thread thread2(thread_proc , "thread2" , &evtQueue);
std::thread thread3(thread_proc , "thread3" , &evtQueue);
std::thread thread4(thread_proc , "thread4" , &evtQueue);
std::thread thread5(thread_proc , "thread5" , &evtQueue);
std::thread thread6(thread_proc , "thread6" , &evtQueue);
for(int i = 0; i < 1000; ++i)
{
if (rand() % 2 == 0)
evtQueue.PushEvent(new Event(static_cast<Event::Type>(rand())));
else
evtQueue.PushEvent(new HelpEvent(std::to_string(rand() % 500) + "--help msg"));
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
for(int i = 0; i < 6; ++i)
{
evtQueue.PushEvent(new QuitEvent(qrand() % 500));
}
thread1.join();
thread2.join();
thread3.join();
thread4.join();
thread5.join();
thread6.join();
std::cout << "All Quit!" << std::endl;
return 0;
}
C++多线程中的条件变量的使用。
在多线程编程中,常常使用条件变量来等待某个事件的发生。
上述代码中,有几个问题需要澄清:
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