C++ 线程条件变量

306 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

C++ 线程条件变量

使用C++线程可以很好的实现线程之间同步的问题。
使用条件变量需要 #include <condition_variable>      

C++ 标准库中有两套实现 std::condition_variable和std::condition_variable_any, 两者都需要与一个互斥量一起才能工作;      

  • std::condition_variable 仅限于与 std::mutex 一起工作
  • std::condition_variable_any 可以和任何满足最低标准的互斥量一起工作,但会产生额外的体积、性能、以及系统资源的开销 使用条件变量可以实现经典的 消费者生产者 问题,代码如下:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <unistd.h>

std::mutex mtx;
std::condition_variable condition;
std::queue<int> q;

void producer()
{
  while(true)
  {
    int num = rand() % 10 + 1;
    sleep(num);
    std::lock_guard<std::mutex> lk(mtx);
    q.push(num);
    condition.notify_one();
  }
}

void consumer()
{
  while (true)
  {
    std::unique_lock<std::mutex> lk(mtx); // 1
    // 当队列 q 为空等待通知
    while(q.empty())
    {
      condition.wait(lk); // 2 阻塞等待通知
    }
    // condition.wait(lk, []{return !q.empty();}); // 该代码与 while... 作用相同
    std::cout << "consumer:" << q.front() << std::endl;
    q.pop();
  }  
}

int main()
{
  std::thread t1(producer);
  std::thread t2(consumer);
  t1.join();
  t2.join();
}

在上述消费者线程中,代码 2 处有有一次解锁(有多个消费者的时候不影响其他线程获取锁,否则锁会被当前的线程一直占有),等待通知后再重新加锁的过程。

  • notify_all()  // 通知所有等待的线程

  • notify_one()  // 通知一个等待线程

C++ 线程的期望

  • std::future 使用 future 可以用来接收线程的返回值,当我们需要拿到这个返回值可以直接调用 future 对象的 get() 方法, future 通常配合 std::async 配合使用
  • std::shared_future 使用 shared_future 可以多次调用 get() 方法, 而 future 仅能调用一次 get() 方法

std::async

  • std::async 区别于 std::thread, 当系统资源紧张的时候,thread 可能创建失败从而导致整程序的崩溃,async 创建异步任务,可能创建线程也可能不创建线程

future 的简单使用示例:

#include <iostream>
#include <future>
 
int func(int a, int b)
{
    return a + b;
}

int main()
{
    std::future<int> f = std::async(func, 3, 4);
    std::cout << f.get() << std::endl;
}

控制 async 是否创建线程或者线程启动的时间

  • std::launch::async 在一个新线程上启动
  • std::launch::deferred 在 wait(), get() 的时候线程执行

代码示例:

std::future<int> f1 = std::async(std::launch::async, function, arg1, arg2); // 在新的线程上启动
f1.wait(); // 阻塞直至可以获取线程的返回值

std::future<int> f2 = std::asyncc(std::launch::deferred, function, arg1, arg2); // 在wait(), get() 的时候调用

f2.wait(); // 开始调用,
f2.get(); // 开始调用

std::future<int> f3 = std::asyncc(std::launch::async|std::launch::deferred, function, arg1, arg2);

C++ 时钟

  • std::chrono::system_clock::now() 返回系统的时间,system_clock 是不稳定的,因为其时钟可调,可能导致当前调用得到的时间早于之前得到的时间。

  • std::chrono::steady_clock::now() C++提供的稳定的时钟(steady_clock)

显示时间代码:

auto now = std::chrono::system_clock::now()
std::cout << std::chrono::duration<double/int/long>(now).count() << std::endl;
  • 可接受超时的函数

    ps: 为什么vscode markdown可以正常显示,掘金这里不可以正常显示

|类型/命名空间 |  函数 |  返回值|

|  ---------- | ----  | ------ |

|std::this_thread[namespace] |  sleep_for(duration)
sleep_until(time_point) |  N/A |

|std::condition_variable 或
std::condition_variable_any|  wait_for(lock, duration)
wait_until(lock, time_point) |  std::cv_status::time_out 或
std::cv_status::no_timeout|

|              | wait_for(lock, duration, predicate)
wait_until(lock, duration, predicate)| bool —— 当唤醒时,返回谓词的结果|

| std::timed_mutex 或
std::recursive_timed_mutex |  try_lock_for(duration)
try_lock_until(time_point) |  bool —— 获取锁时返回true,否则返回fasle |

| std::unique_lock | unique_lock(lockable, duration)
unique_lock(lockable, time_point) |  N/A —— 对新构建的对象调用owns_lock();
当获取锁时返回true,否则返回false |

|            | try_lock_for(duration)
try_lock_until(time_point) | bool —— 当获取锁时返回true,否则返回false |

|std::future或
std::shared_future |wait_for(duration)  | 当等待超时,返回std::future_status::timeout |

|std::future或
std::shared_future | wait_until(time_point) |  当“期望”准备就绪时,返回std::future_status::ready
当“期望”持有一个为启动的延迟函数,返回std::future_status::deferred|

   

C++ promise/packaged_task

promise 和 packaged_task 都需要结合 future 使用;
promise 使用 promise:: set_value 方法去通知future
promoise使用实例代码:

#include <vector>
#include <thread>
#include <future>
#include <numeric>
#include <iostream>
#include <chrono>

void accumulate(std::vector<int>::iterator first,
                std::vector<int>::iterator last,
                std::promise<int> accumulate_promise)
{
    int sum = std::accumulate(first, last, 0);
    accumulate_promise.set_value(sum);  // Notify futurevoid do_work(std::promise<void> barrier)
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    barrier.set_value();
}

int main()

{
    // Demonstrate using promise<int> to transmit a result between threads.
    std::vector<int> numbers = { 1, 2, 3, 4, 5, 6 };
    std::promise<int> accumulate_promise;
    std::future<int> accumulate_future = accumulate_promise.get_future();
    std::thread work_thread(accumulate, numbers.begin(), numbers.end(),
                            std::move(accumulate_promise));
    // future::get() will wait until the future has a valid result and retrieves it.
    // Calling wait() before get() is not needed
    //accumulate_future.wait();  // wait for result

    std::cout << "result=" << accumulate_future.get() << '\n';
    work_thread.join();  // wait for thread completion
 
    // Demonstrate using promise<void> to signal state between threads.
    std::promise<void> barrier;
    std::future<void> barrier_future = barrier.get_future();
    std::thread new_work_thread(do_work, std::move(barrier));
    barrier_future.wait();
    new_work_thread.join();
}

packaged_task 用 future 来接收 task 的结果

使用示例代码:

#include <iostream>
#include <cmath>
#include <thread>
#include <future>
#include <functional>
 
// unique function to avoid disambiguating the std::pow overload set
int f(int x, int y) { return std::pow(x,y); }
 
void task_lambda()
{
    std::packaged_task<int(int,int)> task([](int a, int b) {
        return std::pow(a, b);
    });
    std::future<int> result = task.get_future();
    task(2, 9);
    std::cout << "task_lambda:\t" << result.get() << '\n';
}

void task_bind()
{
    std::packaged_task<int()> task(std::bind(f, 2, 11));
    std::future<int> result = task.get_future();
    task();
    std::cout << "task_bind:\t" << result.get() << '\n';
}

void task_thread()
{
    std::packaged_task<int(int,int)> task(f);
    std::future<int> result = task.get_future();
    std::thread task_td(std::move(task), 2, 10);
    task_td.join();
    std::cout << "task_thread:\t" << result.get() << '\n';
}

int main()
{
    task_lambda();
    task_bind();
    task_thread();
}