目录
C++11对线程支持有很大的提升(参见 C++11线程thread与任务async),可以方便地处理线程。同时提供了互斥量与条件变量,可方便处理类似消费者-生产者问题。
线程
C++11中对线程提供了良好的支持,通过thread即可直接创建线程,同时通过promise,future,packaged_task以及async提供了对线程控制的能力。
thread
通过thread定义一个线程变量,线程就会自动开始执行;变量离开作用域时,要join(等待线程结束)或detach(分离线程,线程在后台独立运行)线程,否则会出错。
使用thread启动线程时,可方便地传递参数(类成员函数的this指针也可以传递);但是参数的默认传递方式是值传递,若要以引用方式则需要使用std::ref或std::cref来封装参数。
#includes <thread>
#includes <future>
void multiNumber(int &nNum){
// ...
nNum *= 2;
}
void multiPtr(int *pNum){
*pNum *= 2;
}
class HelloThread{
public:
void Say(const char *strInfo){
cout<<"Hello "<<strInfo<<endl;
}
void TestThread(){
int nCount = 1;
thread thrMulti(multiNumber, std::ref(nCount));
thrMulti.join();
cout<<"Mulit: "<<nCount<<endl; // 2
thread thrPtr(multiPtr, &nCount); // 指针,需要传递地址
thrPtr.join();
cout<<"Mulit: "<<nCount<<endl; // 4
char myInfo[] = "thread-test";
HelloThread ht;
thread thrHello(&HelloThread::Say, &ht, std::cref(myInfo)); // this为指针,需穿地址
thrHello.join();
}
async
async除能自动创建线程外,还会返回future,以便对线程进行控制(如获取返回值,捕获异常等)。
创建async时,还可以确定线程启动时机:
-
launch::async
:立即开始执行 -
launch::deferred
:等待retFuture.get()时才开始执行
int getMulti(int nNum){
// ...
return nNum * 2;
}
void TestThread(){
auto getNum = std::async(std::launch::async, getMulti, 2);
auto getRet = getNum.get();
cout<<getRet<<endl; // 4
}
sleep
通过slee_for可以方便地进行休眠(yield可让出当前时间片),但是其使用的时间是chrono中的,我们可以重定义一个方便的接口。
class XThread{
public:
static void Sleep(int nSec, int nMillSec){
std::chrono::seconds secs(nSec);
std::chrono::milliseconds mills(nMillSec);
std::this_thread::sleep_for(secs+mills);
}
}
future
future除用作获取线程返回值外,还可作为同步用(一般从promise中获取;在没有设定前,一直是无信号状态)。future不可复制与共享,若要共享则需要使用share_future(类似shared_ptr,通过计数方式实现)。
void FutureWait(std::shared_future<int> &ft){
ft.wait();
cout<<"Wait: "<<ft.get()<<endl;
}
void FutureGet(std::shared_future<int> ft){
ft.get(); // 一直等待,直到设定
cout<<"Wait: "<<ft.get()<<endl;
}
void FutureSet(std::promise<int> &pro){ // 需要修改,必须为引用(本身也不运行复制操作)
// ... sleep a while
cout<<"Future set"<<endl;
pro.set_value(1);
// pro.set_exception(std::current_exception()); // 设定当前抛出的异常(在catch中使用),则get时会重新抛出此异常
}
void TestFuture(){
std::promise<int> pro;
auto ft = pro.get_future();
auto sf = ft.share();
thread thrWait(FutureWait, std::ref(sf));
thread thrGet(FutureGet, sf);
thread thrSet(FutureSet, pro);
thrSet.detach();
thrWait.join();
thrGet.join();
}
互斥量与条件变量
条件变量,能用于阻塞一个/多个线程,直至另一线程修改共享变量(条件)并通知。为了防止race-condition,条件变量总是和互斥锁变量mutex结合在一起使用。
-
执行wait、wait_for或wait_until时,需要持有锁
-
执行notify_one或notify_all时,不需要有锁
互斥量mutex
互斥量mutex可以通过lock_guard或unique_lock方便地进行管理。
class Products{
public:
void Produce(int nId){
std::lock_guard<std::mutex> lker(m_mtx); // 只要离开当前函数,就会释放锁
// ...
}
private:
std::mutex m_mtx;
}
事件Event
通过mutex与condition_variable可以方便地构造事件,注意cv.wait时要使用unique_lock而非lock_guard(因wait时会解除锁定,当获取信号后再锁定)。
#include <mutex>
#include <condition_variable>
#include <chrono>
class XEvent{ // No signal when init
public:
void Wait(){
std::unique_lock<std::mutex> lker(m_mtx);
m_cv.wait(lker);
}
bool Wait(int nSec, int nMillSec){
std::unique_lock<std::mutex> lker(m_mtx);
std::chrono::seconds secs(nSec);
std::chrono::milliseconds mills(nMillSec);
auto ret = m_cv.wait_for(lker, secs+mills);
return (ret != std::cv_status::timeout);
}
void NotifyOne(){
m_cv.notify_one();
}
void NotifyAll(){
m_cv.notify_all();
}
private:
std::mutex m_mtx;
std::condition_variable m_cv;
}
信号量semaphore
在Event基础上,只需再增加一个计数变量,即可方便地实现信号量。
class XSemaphore{
public:
XSemaphore(int nCount=0):m_count(nCount){}
void Wait(){
std::unique_lock<std::mutex> lker(m_mtx);
m_cv.wait(lker, [this](){return m_count>0;});
--m_count;
}
bool Signal(){
{
std::unique_lock<std::mutex> lker(m_mtx);
++m_count;
} // 通知前解锁,避免获取信号后因得不到锁重新进入等待
m_cv.notify_one();
}
private:
int m_count;
std::mutex m_mtx;
std::condition_variable m_cv;
}
读写锁RWLocker
读写锁在多读少写的情形下,非常有用;通过允许读者间可共享获取锁,来提高并发;同时通过互斥写锁,保证数据同步。通过两个条件变量与一个互斥量,可以很好地模拟出读写锁(写者优先锁:有写请求时,优先满足)。
class XWRLocker{
public:
void AcqurieRead(){
std::unique_lock<std::mutex> lker(m_mtxCount);
m_cvRead.wait(lker, [this](){return m_writeCount==0;}); // 只要没有写请求,读请求就可被满足
++m_readCount;
}
void ReleaseRead(){
bool bNotify = false;
{
std::unique_lock<std::mutex> lker(m_mtxCount);
if(--m_readCount==0 && m_writeCount>0){
bNotify=true;
}
}
if(bNotify) // 通知前解锁,避免获取信号后因得不到锁重新切换线程
m_cvWrite.notify_one();
}
void AcqurieWrite(){
std::unique_lock<std::mutex> lker(m_mtxCount);
++m_writeCount; // 获取锁前,先声明写请求
m_cvWrite.wait(lker, [this](){return m_readCount==0 && !m_isWriting;}); // 当前没有读者与写者时,请求才会满足
m_isWriting=true;
}
void ReleaseWrite(){
auto nCount=0;
{
std::unique_lock<std::mutex> lker(m_mtxCount);
m_isWriting=false;
nCount=--m_writeCount;
}
if(nCount==0){ // 没有写请求,直接触发可读信号
m_cvRead.notify_all();
}
else{
m_cvWrite.notify_one();
}
}
private:
bool m_isWriting{false};
unsigned m_readCount{0};
unsigned m_writeCount{0};
std::mutex m_mtxCount;
std::condition_variable m_cvRead;
std::condition_variable m_cvWrite;
}