深入理解C++11(十)(3)

深入理解C++11(十)(3)

原子变量

C++11提供了一个原子类型std::atomic,可以使用任意类型作为模板参数,C++11内置了整型的原子变量,可以更方便地使用原子变量,使用原子变量就不需要使用互斥量来保护该变量了,用起来更简洁。例如,要做一个计数器,使用mutex时是这样的,如代码清单5-7所示:

struct Counter
{
int value;
std::mutex mutex;
void increment()
{
std::lock_guard<std::mutex> lock(mutex);
++value;
}
void decrement()
{
std::lock_guard<std::mutex> lock(mutex);
--value;
}
int get()
{
vreturn value
}
};

如果使用原子变量,就不需要再定义互斥量了,使用更简便,如代码清单5-8所示:

#include <atomic>
struct AtomicCounter {
std::atomic<int> value;
void increment(){
++value;
}
void decrement(){
--value;
}
int get(){
return value.load();
}

call_once/once_flag的使用
为了保证在多线程环境中某个函数仅被调用一次,比如,需要初始化某个对象,而这个对象只能初始化一次时,就可以用std::call_once来保证函数在多线程环境中只被调用一次。使用std::call_once时,需要一个once_flag作为call_once的入参,它的用法比较简单。

#include <iostream>
#include <thread>
#include <mutex>
std::once_flag flag;
void do_once()
{
std::call_once(flag, [](){ std::cout << "Called once" << std::endl; });
}
int main()
{
std::thread t1(do_once);
std::thread t2(do_once);
std::thread t3(do_once);
t1.join();
t2.join();
t3.join();
}

输出结果如下:
Called once

异步操作
C++11提供了异步操作相关的类,主要有std::future、std::promise和std::package_task。std::future作为异步结果的传输通道,可以很方便地获取线程函数的返回值;std::promise用来包装一个值,将数据和future绑定起来,方便线程赋值;std::package_task用来包装一个可调用对象,将函数和future绑定起来,以便异步调用。

获取线程函数返回值的类std::future
C++11中增加的线程,使得我们可以非常方便地创建和使用线程,但有时会有些不便,比如希望获取线程函数的返回结果,就不能直接通过thread.join()得到结果,这时就必须定义一个变量,在线程函数中去给这个变量赋值,然后执行join(),最后得到结果,这个过程是比较烦琐的。thread库提供了future用来访问异步操作的结果,因为一个异步操作的结果不能马上获取,只能在未来某个时候从某个地方获取,这个异步操作的结果是一个未来的期待值,所以被称为future,future提供了获取异步操作结果的通道。我们可以以同步等待的方式来获取结果,可以通过查询future的状态(future_status)来获取异步操作的结果。future_status有如下3种状态:

·Deferred,异步操作还没开始。
·Ready,异步操作已经完成。
·Timeout,异步操作超时。

我们可以查询future的状态,通过它内部的状态可以知道异步任务的执行情况,比如下面的代码将不断查询future的状态,直到任务完成为止。

// 查询future的状态
std::future_status status;
do {
status = future.wait_for(std::chrono::seconds(1));
if (status == std::future_status::deferred) {
std::cout << "deferred\n";
} else if (status == std::future_status::timeout) {
std::cout << "timeout\n";
} else if (status == std::future_status::ready) {
std::cout << "ready!\n";
}
} while (status != std::future_status::ready);

获取future结果有3种方式:get、wait、wait_for,其中get等待异步操作结
束并返回结果,wait只是等待异步操作完成,没有返回值,wait_for是超时等
待返回结果。

协助线程赋值的类std::promise
std::promise将数据和future绑定起来,为获取线程函数中的某个值提供便利,在线程函数中为外面传进来的promise赋值,在线程函数执行完成之后就可以通过promis的future获取该值了。值得注意的是,取值是间接地通过promise内部提供的future来获取的。std::promise的基本用法如下:

std::promise<int> pr;
std::thread t([](std::promise<int>&
p){ p.set_value_at_thread_exit(9); },std::ref(pr));
std::future<int> f = pr.get_future();
auto r = f.get();

可调用对象的包装类std::package_task
std::packaged_task包装了一个可调用对象的包装类(如function、lambda expression、bind expression和another function object),将函数和future绑定起来,以便异步调用,它和std::promise在某种程度上有点像,promise保存了一个共享状态的值,而packaged_task保存的是一个函数。std::promise的基本用法如下:

std::packaged_task<int()> task([](){ return 7; });
std::thread t1(std::ref(task));
std::future<int> f1 = task.get_future();
auto r1 = f1.get();

std::promise、std::packaged_task和std::future三者之间的关系
std::future提供了一个访问异步操作结果的机制,它和线程是一个级别的,属于低层次的对象。在std::future之上的高一层是std::packaged_task和std::promise,它们内部都有future以便访问异步操作结果,std::packaged_task包装的是一个异步操作,而std::promise包装的是一个值,都是为了方便异步操作,因为有时需要获取线程中的某个值,这时就用std::promise,而有时需要获一个异步操作的返回值,这时就用std::packaged_task。那么std::promise和std::packaged_task之间又是什么关系呢?可以将一个异步操作的结果保存到std::promise中。

future被promise和package_task用来作为异步操作或者异步结果的连接通道,用std::future和std::shared_future来获取异步调用的结果。future是不可拷贝的,只能移动,shared_future是可以拷贝的,当需要将future放到容器中则需要用shared_future。

package_task和shared_future的基本用法如代码清单5-9所示:

// packaged_task::get_future
#include <iostream>
#include <utility>
#include <future>
#include <thread>
// a simple task:
int func (int x) { return x+2; }
int main ()
{
std::packaged_task<int(int)> tsk (func);
std::future<int> fut = tsk.get_future(); // 获取
future
std::thread(std::move(tsk),2).detach(); // task作为线程函数
int value = fut.get(); // 等待
task完成并获取结果
std::cout <<"The result is "<< value <<".\n";
// std::future是不能复制的,不能放到容器中,需要用
shared_future
vector<std::shared_future<int>> v;
auto f = std::async(std::launch::async,
[](int a,int b){return a + b;},2,3);
v.push_back(f);
std::cout << "The shared_futureresult is"<<v[0].get()<<'\n';
return 0;
}

输出结果如下:
The result is 4
The shared_futureresult is 5

线程异步操作函数async
std::async比std::promise、std::packaged_task和std::thread更高一层,它可以用来直接创建异步的task,异步任务返回的结果也保存在future中,当需要获取异步任务的结果时,只需要调用future.get()方法即可,如果不关注异步任务的结果,只是简单地等待任务完成的话,则调用future.wait()方法。

现在看一下std::async的原型async(std::launch::async|std::launch::deferred,f,args…),第一个参数是线程的创建策略,有两种策略,默认的策略是立即创建线程。

·std::launch::async:在调用async时就开始创建线程。
·std::launch::deferred:延迟加载方式创建线程。调用async时不创
建线程,直到调用了future的get或者wait时才创建线程。

第二个参数是线程函数,第三个参数是线程函数的参数。
std::async的基本用法如代码清单5-10所示:

std::future<int> f1 = std::async(std::launch::async, [](){
return 8;
});
cout<<f1.get()<<endl; // output: 8
std::future<int> f2 = std::async(std::launch::async, [](){
cout<<8<<endl;
});
f2.wait(); // output: 8
std::future<int> future = std::async(std::launch::async, [](){
std::this_thread::sleep_for(std::chrono::seconds(3));
return8;
});
std::cout <<"waiting...\n";
std::future_status status;
do {
status = future.wait_for(std::chrono::seconds(1));
if (status == std::future_status::deferred) {
std::cout <<"deferred\n";
} elseif (status == std::future_status::timeout) {
std::cout <<"timeout\n";
} elseif (status == std::future_status::ready) {
std::cout <<"ready!\n";
}
} while (status != std::future_status::ready);
std::cout <<"result is "<< future.get() <<'\n';

可能的结果如下:
waiting...
timeout
timeout
ready!
result is8

std::async是更高层次的异步操作,使得我们不用关注线程创建内部细节,就能方便地获取异步执行状态和结果,还可以指定线程创建策略:应该用std::async替代线程的创建,让它成为我们做异步操作的首选。

总结
C++11以前是没有内置多线程的,使得跨平台的多线程开发不方便,现在C++11增加了很多线程相关的库,使得我们能很方便地开发多线程程序了。

·线程的创建和使用简单方便,可以通过多种方式创建,还可以根据需
要获取线程的一些信息及休眠线程。
·互斥量可以通过多种方式来保证线程安全,既可以用独占的互斥量保
证线程安全,又可以通过递归的互斥量来保护共享资源以避免死锁,还可以
设置获取互斥量的超时时间,避免一直阻塞等待。
·条件变量提供了另外一种用于等待的同步机制,它能阻塞一个或多个
线程,直到收到另外一个线程发出的通知或者超时,才会唤醒当前阻塞的线
程。条件变量的使用需要配合互斥量。
·原子变量可以更方便地实现线程保护。
·call_once保证在多线程情况下函数只被调用一次,可以用在在某些只能
初始化一次的场景中。
·future、promise和std::package_task用于异步调用的包装和返回值。
·async更方便地实现了异步调用,应该优先使用async取代线程的创建。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值