线程同步,即在一个线程完成前,需要等待一个特定的事情发生,或者等待一个条件达成。在条件变量(condition_variable)和期望(future)中可实现线程同步的操作。
在condition_variable头文件中,包含了可以用于等待(wait)和通知(notify)系列函数。
等待函数 | 解释 |
---|---|
wait() | 阻塞该线程,直到被通知唤醒 |
wait_for() | 阻塞该线程,直到等待超时或被通知唤醒 |
wait_until() | 阻塞该线程,直到某时间点或被通知唤醒 |
通知函数 | 解释 |
---|---|
notify_one() | 唤醒一个当前正在等待的同一condition_variable对象的线程 |
notify_all() | 唤醒所有当前正在等待的同一condition_variable对象的线程 |
#include // std::cout
#include // std::thread
#include // std::mutex, std::unique_lock
#include // 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 lck(m);
cv1.wait(lck);
...
}
void bar(){
...
lock_gound 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头文件包含允许异步操作的系列函数和类。
异步调用函数async调用一个fn,返回一个future对象持有fn的返回结果,返回时不等待fn的执行完成。
也可以传入一个额外的参数,这个参数类型是std::launch,用来控制fn何时被调用:
参数 | 描述 |
---|---|
launch::async | 为函数创建一个独立的线程,立即执行 |
launch::deferred | 函数的调用延迟到wait()或get()函数调用时才执行 |
launch::deferred | launch::async | 两种方式都可以(默认) |
例:
#include
#include
using namespace std;
bool is_prime(int x){
for(int i=2;iif(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类包装一个函数,并允许异步检查其结果,并将结果保存到一个future对象中,通过get_future()获得。
packaged_task<>的模版参数试一个函数签名,比如一个函数int foo(double, std::string&);
在为foo包装的时候,构造packaged_task<>对象时就必须特化为packaged_task
同样,这样构造的对象pt在调用get_future()时返回的future对象就应该是future
型的。
packaged_task中包装的任务可以通过两种方式执行:
1. 使用packaged_task重载的operator()传参调用
2. 创建thread对象,将任务和参数传入调用
例:
#include
#include
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 对象可以保存某一类型 T 的值,该值可被 future 对象读取(可能在另外一个线程中),因此 promise 也提供了一种线程同步的手段。
可以通过 get_future 来获取与该 promise 对象相关联的 future 对象,调用该函数之后,两个对象共享相同的共享状态。
可以通过set_value来设定相关联的promise对象和future对象之间的共享值。当promise对象没有设定时,调用future.get()时线程将会被阻塞。当set_value()设置完毕,对应的future由阻塞态变为就绪态,并且可以用于检索已存储的值。
例:
#include
#include
#include
using namespace std;
void print_future(future<int>& f){
cout << "inside future"<cout << "get "<//获得期望值。在未设置之前将会阻塞该线程
}
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;
}