c/c++条件变量的使用注意要点

正文

多线程下使用条件变量需要注意一些要点

  • 1.消费者线程尽量使用time_wait,去定期检查消费者队列长度,这可以防止一些notify丢失的问题而导致的消费者block,譬如说在cpu调度上:
    如果先进行了notify,然后此时消费者线程才开始运行执行了wait,此时notify是消失的,消费者线程将会永远阻塞, 一个常见的错误程序如下,看上去正确的程序由于无法保证thread能够中的wait能够先运行,可能在wait的时候fn还Block在unique_lock下, notify()的时候还没有执行到wait()导致子线程永远阻塞到wait()上。(条件变量和信号量是有区别的,条件变量的notify实质是发送一个信号,并不具有++的持久性)
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
 
std::mutex m;
std::condition_variable cv;
 
void fn(){
    std::cout<<"fn start\n";

    std::unique_lock<std::mutex> lck(m);
    cv.wait(lck);
    std::cout<<"fn finish\n";
}

int main()
{

    std::thread worker(fn);
    {
        std::cout<<"sleep start\n";
        std::lock_guard<std::mutex> lck(m);
        std::cout<<"notify start\n";
        cv.notify_one();
        std::this_thread::sleep_for(std::chrono::seconds(3));
        std::cout<<"sleep finish\n";
    }
    std::cout<<"lock free\n";
    worker.join();
    return 0;
}

notify丢失的问题,涉及到异步线程无法保证时序性的情况下,最好的处理方式是使用超时检测,去判断是否已经处于结束状态,c++中提供了wait_for, 一个伪代码示例

std::condition_variable cv;
std::mutex m;
void fn(){
  while(1){
      std::unique_lock<std::mutex> lk(cv_m);
      cv.wait_for(lk, 100ms);
      if(quit){
          return;
      }
      //do..task
  }
}
  • 2.消费者线程wait调用结束时外层一定要使用while循环去校验消费者队列情况,因为存在一些虚假唤醒,以及一些惊群唤醒的情况, 避免接下来的消费逻辑处理出现异常
std::unique_lock<std::mutex> lck(m);

while(queue.len() == 0){
      cv.wait(lck);
}

  • 3.尽量用notify_one去避免多线程惊群效应

  • 4.notify时要放在lock外面,notify的本质是发送一个信号告知消费者线程,其接收到信号后就会去lock对应的mutex,如果此时mutex获取不到,就白白的浪费了一次调度(拿不到锁陷入阻塞,直到后续消费者线程释放锁了之后才拿到)

  • 5.现代c++的写法
    c++11之后支持如下写法:

template< class Predicate >
void wait( std::unique_lock<std::mutex>& lock, Predicate stop_waiting );

等价于

while (!stop_waiting())
{
    wait(lock);
}

所以就可以这么写了

std::unique_lock<std::mutex> lock(mutex);

hasFrame.wait(lock, [this]() { return !FrameList.empty() || _quit; });

AVFramePtr ret = _avFrameList.front();
posted @ 2024-01-14 00:02  woder  阅读(23)  评论(0编辑  收藏  举报