欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

【并发编程】多线程程序同步策略

程序员文章站 2022-03-20 22:09:21
以C++11为例,介绍多线程并发编程的同步策略。包含了条件变量的使用、线程安全的队列、std::future、std::promise等。 ......

c++11线程使用初探

std::thread

#include <thread>

只读的共享数据在多个线程间不存在race condition的危险,而可读可写共享数据在线程间共享时则需做好线程同步,即数据保护,主要包括lock-based和lock-free策略。

常见的以互斥锁保护多线程间的共享数据,保证某一时刻仅有一个线程访问共享数据,导致线程间数据保护是串行,因此在多线程环境中,锁保护的区域越小,并发程度越高。

采用条件变量等待某个事件或条件发生

c++11提供了两种条件变量:std::condition_variable和std::condition_variable_any,均需要和互斥量一起使用来保证操作的同步性。前者仅能与std::mutex一起使用,后者可与所有mutex-like的锁一起使用,更加通用,但以牺牲空间、性能或操作系统资源为代价,因此std::condition_variable是首选;它们均在头文件中定义。

生产者-消费者模式在并发编程中应用广泛,有助于系统的解耦。队列是一种常见的在生产者和消费者线程间传递数据的容器,队列先入先出的特性满足应用对顺序性的要求。

人脸分析组件采用队列传递数据,通过在生产者线程中调用inputdata函数将待分析数据包送入队列,并调用notify_one通知消费者线程,从而消费者线程进行人脸数据分析。伪代码如下:

std::mutex mut;
std::condition_variable cond;
std::queue<data_chunk> data_queue;

// 将待分析数据送入队列
int inputdata(const data_chunk& data)
{
  if (invalid_data)
  {
    log_error("invalid param!");
    return error_code;
  }
  // 将数据送入队列
  // 并发出通知
  std::lock_guard<std::mutex> lk(mut);
  data_queue.push(data);
  cond.notify_one();
  return success_code;
}

// inputdata在生产者线程中被调用,将待分析数据送入队列
// 消费线程函数process从队列取数据以进行分析
// 线程在条件未发生时因wait函数阻塞进入睡眠状态
void process()
{
  while (not_exit_expression)
  {
    // 使用unique_lock而非lock_guard
    std::unique_lock<std::mutex> lk(mut);

    // wait函数在条件满足时返回,否则线程进入阻塞状态
    //
    // 若lambda表达式返回false(即不条件满足),则wait释放lk中锁资源,
    // 且线程进入阻塞状态,以便生线程可以继续获取锁并送数据到队里;否则,
    //
    // 当notify_one通知条件变量时,消费消除从睡眠状态苏醒,重新获取锁,且对条件再次检查,lambda表达式返回true
    // 此时,wait返回并继续持有锁资源,然后继续往下执行
    cond.wait(lk, [](){ return !data_queue.empty(); });

    data_chunk data = data_queue.front();
    data_queue.pop();

    // wait返回后持有锁,因此需要在此处解锁
    lk.unlock();

    // 处理数据
    process_the_data;
  }
}