C++11中线程及信号量与条件变量

目录

线程

thread

async

sleep

future

互斥量与条件变量

互斥量mutex

事件Event

信号量semaphore

读写锁RWLocker


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;
}

  • 6
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值