线程同步操作

线程同步,即在一个线程完成前,需要等待一个特定的事情发生,或者等待一个条件达成。在条件变量(condition_variable)和期望(future)中可实现线程同步的操作。


condition_variable

在condition_variable头文件中,包含了可以用于等待(wait)和通知(notify)系列函数。

等待函数解释
wait()阻塞该线程,直到被通知唤醒
wait_for()阻塞该线程,直到等待超时或被通知唤醒
wait_until()阻塞该线程,直到某时间点或被通知唤醒
通知函数解释
notify_one()唤醒一个当前正在等待的同一condition_variable对象的线程
notify_all()唤醒所有当前正在等待的同一condition_variable对象的线程

例子


#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_id (int id) {
  std::unique_lock<std::mutex> lck(mtx);
  while (!ready) cv.wait(lck);
  // ...
  std::cout << "thread " << id << '\n';
}

void go() {
  std::unique_lock<std::mutex> lck(mtx);
  ready = true;
  cv.notify_all();
}

int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(print_id,i);

  std::cout << "10 threads ready to race...\n";
  go();                       // go!

  for (auto& th : threads) th.join();

  return 0;
}

代码中,在print_id函数中,首先对ready上锁,判断ready的值。当ready不为true的时候,使用条件变量cv来阻塞该线程,并解锁该互斥量,让其他线程能够访问。go()改变ready后,调用条件变量cv的notify_all()来通知所有cv阻塞的线程,它们将被唤醒,重新进入对信号量的竞争状态,获得互斥锁,执行下面的操作。
值得一提的是,notify()系列函数只会对被同一condition_variable对象wait()的线程进行通知操作。如:

mutex m;
condition_variable cv1,cv2;
void foo(){
    ...
    lock_gound<mutex> lck(m);
    cv1.wait(lck);
    ...
}
void bar(){
    ...
    lock_gound<mutex> lck(m);
    cv2.wait(lck);
    ...
}
void cv_notify(){
    ...
    cv1.notify_one();
    ...
}
int main(){
    thread t1(foo);
    thread t2(bar);
    thread t3(cv_notify);
    t1.join();
    t2.join();
    t3.join();
    return 0;
}

上述代码中,foo()被cv1阻塞,bar被cv2阻塞,cv_notify()唤醒被cv1阻塞的一个线程。那么foo()将被唤醒,而bar()只能等待另一个使用cv2.notify_one()的线程将它唤醒。


future

future头文件包含允许异步操作的系列函数和类。

async——异步调用函数

异步调用函数async调用一个fn,返回一个future对象持有fn的返回结果,返回时不等待fn的执行完成。
也可以传入一个额外的参数,这个参数类型是std::launch,用来控制fn何时被调用:

参数描述
launch::async为函数创建一个独立的线程,立即执行
launch::deferred函数的调用延迟到wait()或get()函数调用时才执行
launch::deferred | launch::async两种方式都可以(默认)

例:

#include<iostream>
#include<future>
using namespace std;
bool is_prime(int x){
    for(int i=2;i<x;i++){
        if(x%i==0){
            return false;
        }
    }
    return true;
}
int main(){
    future<bool> future1 = async(is_prime,21);    // ①
    ...
    bool ret =future1.get();    // ②
    if(ret){
        cout << "21 is prime";
    }else{
        cout << "21 is not prime";
    }
    return 0;
}

主函数中,①调用async函数创建一个线程执行is_prime()函数,并传入21作为参数,返回一个特化为bool的future对象。然后执行其他自己的操作。当需要获得函数is_prime的返回结果时,②通过future1 的get()获得返回值,赋值给ret。

packaged_task<>——任务

packaged_task类包装一个函数,并允许异步检查其结果,并将结果保存到一个future对象中,通过get_future()获得。
packaged_task<>的模版参数试一个函数签名,比如一个函数int foo(double, std::string&);在为foo包装的时候,构造packaged_task<>对象时就必须特化为packaged_task<int(double, string)> pt(foo);同样,这样构造的对象pt在调用get_future()时返回的future对象就应该是future<int>型的。

packaged_task中包装的任务可以通过两种方式执行:
1. 使用packaged_task重载的operator()传参调用
2. 创建thread对象,将任务和参数传入调用

例:

#include<iostream>
#include<future>

using namespace std;

bool is_prime(int x) {

    for (int i = 2; i < x; i++) {
        if (x % i == 0) {
            return false;
        }
    }
    return true;
}

int main() {
    int x1 = 41, x2 = 42;
    //通过operator()方法调用pt1的函数
    packaged_task<bool(int)> pt1(is_prime);
    pt1(x1);
    auto ret1 = pt1.get_future().get();
    if (ret1) {
        cout << x1 << " is prime" << endl;
    } else {
        cout << x1 << " is not prime" << endl;
    }
    //通过新建线程方法调用pt2的函数
    packaged_task<bool(int)> pt2(is_prime);
    future<bool> f = pt2.get_future();
    thread t(move(pt2), ref(x2));
    auto ret = f.get();
    if (ret) {
        cout << x2 << " is prime" << endl;
    } else {
        cout << x2 << " is not prime" << endl;
    }
    t.join();
    return 0;
}

主函数中第一块代码通过packaged_task<>的operator()来调用任务包装的函数,在本线程执行。它可以接收多个参数,传递给包装的函数,然后通过返回的future实例获得函数的运行结果。第二块代码通过创建线程的方式调用包装的函数,在其他线程执行,同样通过future获得结果。

promise——承诺

promise 对象可以保存某一类型 T 的值,该值可被 future 对象读取(可能在另外一个线程中),因此 promise 也提供了一种线程同步的手段。
可以通过 get_future 来获取与该 promise 对象相关联的 future 对象,调用该函数之后,两个对象共享相同的共享状态。
可以通过set_value来设定相关联的promise对象和future对象之间的共享值。当promise对象没有设定时,调用future.get()时线程将会被阻塞。当set_value()设置完毕,对应的future由阻塞态变为就绪态,并且可以用于检索已存储的值。
例:

#include<iostream>
#include<future>
#include<chrono>
using namespace std;

void print_future(future<int>& f){
    cout << "inside future"<<endl;    
    cout << "get "<<f.get()<<endl;    //获得期望值。在未设置之前将会阻塞该线程
}

int main() {
    promise<int> prom;    //声明promise对象
    future<int> fut = prom.get_future();    //获得与之相关联的future
    thread t(print_future,ref(fut));        //将future对象传入线程
    this_thread::sleep_for(chrono::seconds(2));    //为显示效果,延迟2秒
    prom.set_value(20);                     //设置期望值
    t.join();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值