目录
thread库为C++增加了线程处理的能⼒,它提供简明清晰的互斥量、线程、条件变量等概念,⽤ 它可以很容易地创建多线程应⽤程序。thread库是⾼度可移植的库,它兼容C++标准,⽀持使⽤ 最⼴泛的POSIX线程和Windows线程,⽤它编写的代码⽆须修改就可以在UNIX、Windows等操作 系统上编译运⾏。
一、Mutex互斥量
互斥量(mutex)是⼀种⽤于线程同步的⼿段,它可以在多线程环境⾥防⽌多个线程同时操作共享资源。⼀旦⼀个线程锁定了互斥量,那么其他线程必须等待它解锁互斥量后才能访问共享资 源。
thread提供了6种互斥量类型(除了互斥功能不同,它们的基本接⼝都很相似):
- null_mutex:⽆任何锁定功能的互斥量,它是空对象模式的应⽤。
- mutex:独占式的互斥量,是最简单、最常⽤的互斥量类型。
- timed_mutex:独占式的互斥量,但提供超时锁定功能。
- recursive_mutex:递归式互斥量,可以多次锁定,相应地也需要多次解锁。
- recursive_timed_mutex:递归式互斥量,同样增加了超时锁定功能。
- shared_mutex:multiple-reader/single-writer型的共享互斥量(⼜称读写锁)。
1.成员函数
成员函数lock()⽤于线程阻塞等待直⾄获得互斥量的所有权(锁定);
当线程使⽤完共享资源后应该及时使⽤unlock()解除对互斥量的锁定。
try_lock()尝试锁定互斥量,如果锁定成功则返回true,否则返回false,它是⾮阻塞的。
直接使⽤mutex的成员函数来锁定互斥量不够⽅便,⽽且在发⽣异常导致退出作⽤域等情况下, 很可能会导致未调⽤unlock(),所以通常要使⽤try-catch块保证解锁互斥量。
//--------------------------.h文件
#pragma once
#define BOOST_THREAD_VERSION 5
#include <iostream>
#include <vector>
#include <map>
#include <boost/lexical_cast.hpp>
#include <boost/thread.hpp>
#include <boost/thread/lock_factories.hpp>
#include <boost/thread/lockable_adapter.hpp>
#include <boost/thread/thread_guard.hpp>
#include <boost/thread/scoped_thread.hpp>
#include <boost/thread/shared_lock_guard.hpp>
class StudyThreadClass
{
public:
void Test();
private:
std::map<int,std::string> _map;
std::vector<boost::thread> thread_vec;
void MutexStudy(int threadId);
};
//--------------------------.cpp文件
boost::mutex _mutex;
void StudyThreadClass::Test()
{
for (size_t i = 0; i < 10000; i++) //初始化vector
{
_map[i] = "hello world";
}
//to do 创建线程调用MutexStudy
}
void StudyThreadClass::MutexStudy(int threadId)
{
while (_map.size() > 0) {
try {
_mutex.lock();
std::string msg = boost::lexical_cast<std::string>(index);
_map[index] = msg;
std::cout << "线程id:" << threadId << "," << _map[index] << "," << _map.size() << std::endl;
_map.erase(index);
index++;
Sleep(10);
_mutex.unlock();
}
catch (...) //使用try-catch保证解锁互斥量
{
_mutex.unlock();
}
}
}
成员函数try_lock_for()和try_lock_until()为try_lock()增加了时间等待的功能:在这段时间内如果获得了锁那么函数会⽴即返回true,否则超时返回false,表示未获得锁,线程此时就可以去做其他的事情。等待时间可以是从当前开始的相对时间(时间⻓度),也可以是⼀个绝对时间点。
void StudyThreadClass::MutexStudy(int threadId){
while (_map.size() > 0) {
boost::chrono::milliseconds _milliseconds(200);
bool flag = _mutex.try_lock_for(_milliseconds);
if (flag) {
std::string msg = boost::lexical_cast<std::string>(index);
_map[index] = msg;
std::cout << "线程id:" << threadId << "," << _map[index] << std::endl;
_map.erase(index);
index++;
_mutex.unlock();
Sleep(100);
}
else {
std::cout << "阻塞...线程id:" << threadId << "," << index << std::endl;
}
}
}
2.扩展(包装mutex)
(1)lock_guard
thread库提供RAII型的lock_guard类⽤于辅助锁定互斥量。它在构造时锁定互斥量,在析构时⾃ 动解锁,从⽽保证了互斥量的正确操作,避免遗忘解锁,它就像⼀个智能指针。
void StudyThreadClass::MutexStudy(int threadId){
while (_map.size() > 0) {
boost::lock_guard<boost::mutex> _lock_guard(_mutex); //注意对比,不需要解锁
std::string msg = boost::lexical_cast<std::string>(index);
_map[index] = msg;
std::cout << "线程id:" << threadId << "," << _map[index] << std::endl;
_map.erase(index);
index++;
Sleep(100);
}
}
(2)unique_lock
unique_lock的⼯作机制与lock_guard相同,它也使⽤了RAII在构造函数⾥锁定,在析构函数⾥解 锁,但它的构造函数还可以接收其他的锁定选项,从⽽有不同的⾏为。
unique_lock也不允许拷⻉,但它内部使⽤指针⽽不是引⽤来保存互斥量,这使得它可以被转移, ⽤起来更灵活。 unique_lock还拥有与mutex相同的接⼝,所以允许程序员任意控制锁定/解锁的时机,⽽且最终 unique_lock的析构函数会保证正确处理互斥量。
thread库⾥有3个锁定选项,它们都是空类,仅作为运⾏时的标志,此外没有其他作⽤,在unique_lock的构造函数⾥使⽤下列常量标志就可以使unique_lock产⽣不同的效果,可以在后续执⾏其他的锁定/解锁操作(在并发环境不可以用这三个锁定选项)。
- defer_lock:is_locked=false,不执⾏锁定操作。
- try_to_lock:is_locked=false,执⾏try_lock。
- adopt_lock:is_locked=true,不执⾏锁定操作。
void StudyThreadClass::MutexStudy(int threadId){
while (_map.size() > 0) {
boost::unique_lock<boost::mutex> _unique_lock(_mutex); //注意对比,也不需要解锁
std::string msg = boost::lexical_cast<std::string>(index);
_map[index] = msg;
std::cout << "线程id:" << threadId << "," << _map[index] << std::endl;
_map.erase(index);
index++;
Sleep(100);
}
}
因为unique_lock⽀持转移语义,所以它可以从⼯⼚函数make_unique_lock产⽣,利⽤模板函数⾃动推导类型的特性 可以不必写出模板参数,这进⼀步简化了编写代码的⼯作 。
void StudyThreadClass::MutexStudy(int threadId){
while (_map.size() > 0) {
auto _unique_lock= boost::make_unique_lock(_mutex); //注意对比
std::cout << "是否锁定了互斥量:" << _unique_lock.owns_lock() << std::endl;
std::string msg = boost::lexical_cast<std::string>(index);
_map[index] = msg;
std::cout << "线程id:" << threadId << "," << _map[index] << std::endl;
_map.erase(index);
index++;
Sleep(100);
}
}
3.自定义互斥量
lock_guard和unique_lock⼤多数情况下需要搭配mutex使⽤,⽤于锁定互斥量,但因为它们是模板类,所以只要是符合“Lockable”概念——也就是有lock/unlock/try_lock接⼝的类(不⼀定是互 斥量),都可以⽤于lock_guard和unique_lock,这样就能够很容易地锁定整个对象,实现原⼦性的事务操作。
thread库定义了⼏个lockable适配器类,⽅便我们实现Lockable概念。
- basic_lockable_adapter:最简单的接⼝,提供lock/unlock。
- lockable_adapter:基本的接⼝,增加try_lock功能。
- timed_lockable_adapter:增加try_lock_for/try_lock_until。
适配器类需要在模板参数⾥指定要适配的mutex类型,以继承的⽅式使⽤,⼦类⾃动获得它们的 lock接⼝,然后就可以应⽤于lock_guard和unique_lock。
class MyLockableAddapter :public boost::lockable_adapter<boost::mutex> {
//可以是空类。使⽤lockable_adapter<mutex>作为基类,所以它具有了和mutex相同的接⼝,可以像使⽤⼀个互斥量那样使⽤它
private:
int value = 0;
public :
void Add(int i)
{
value += i;
}
void Sub(int i)
{
value -= i;
}
int GetValue()
{
return value;
}
};
MyLockableAddapter myLockableAddapter;
void StudyThreadClass::MutexStudy(int threadId){
auto _unique_lock = boost::make_unique_lock(myLockableAddapter);
while (myLockableAddapter.GetValue() == 0)
{
std::string msg = boost::lexical_cast<std::string>(index);
_map[index] = msg;
std::cout << "线程id:" << threadId << "," << _map[index] << std::endl;
_map.erase(index);
index++;
myLockableAddapter.Add(10);
Sleep(100);
}
}
二、thread线程对象
thread类实现了操作系统⾥的线程表示,负责启动和管理线程对象,它在概念和操作上都与 POSIX线程很相似。thread对象是不可拷⻉、不可⽐较的,但它⽀持转移(move)语义,有转移构造函数和转移赋值函数,所以它可以从⼯⼚函数产⽣。
成员函数get_id()可以返回thread::id对象,它提供了完整的⽐较操作符和流输出操作,唯⼀ 地标识了thread对象。
静态成员函数hardware_concurrency()可以获得系统可并发的线程数量,即CPU内核数量。
静态成员函数physical_concurrency()可以获得真实的物理CPU核⼼数,这两个函数如果⽆法获取信息则 都返回0。
4个⾃由函数get_id()、yield()、sleep_for()和sleep_until(),它们⽆须使⽤thread 对象就可以操作当前线程,但它们位于⼦名字空间boost::this_thread,⽽不是thread的静态成 员函数。
- get_id():与thread同名的函数,⽤来获得thread::id。
- yield():指示当前线程放弃时间⽚,允许其他的线程运⾏。
- sleep_for():线程睡眠等待⼀⼩段时间。
- sleep_until():线程睡眠等待⾄⼀个时间点
void StudyThreadClass::MutexStudy(int Id){
//...
}
void Test{
boost::thread thread(&StudyThreadClass::MutexStudy, this, 1);
std::cout << "thread_id:" << thread.get_id() << std::endl;
std::cout <<"cpu内核:"<< thread_vec.hardware_concurrency() << std::endl;
std::cout <<"物理cpu核心数:"<< thread.physical_concurrency() << std::endl;
}
从某种程度上来说,线程就是在进程的另⼀个空间⾥运⾏的⼀个函数,因此线程的创建需要传递给thread对象⼀个⽆参的可调⽤物——函数、函数对象、bind表达式、lambda表达式,它必须具有operator()以供线程执⾏。
如果可调⽤物不是⽆参的,那么thread的构造函数也⽀持直接传递所需的参数,这些参数将被拷⻉并在发⽣调⽤时传递给函数。
在传递参数时需要注意,thread使⽤的是参数的拷⻉,因此要求可调⽤物和参数类型都⽀持拷⻉构造。如果希望将参数传递给线程引⽤值就需要使⽤ref库进⾏包装,同时必须保证被引⽤的对象在线程执⾏期间⼀直存在,否则会引发未定义⾏为。
thread对象在析构时会调⽤std::terminate结束线程的执⾏,它并不关⼼线程是否执⾏完毕,所 以如果要保证函数正确运⾏必须调⽤join()等待线程执⾏结束,或者调⽤detach()分离线程体。
1.创建线程对象
当成功创建了⼀个thread对象后,线程就⽴刻开始执⾏,thread不提供类似start( )、begin( )那样的⽅法。
2.等待线程
thread的成员函数joinable()可以判断thread对象是否标识了⼀个可执⾏的线程体。如果 joinable()返回true,我们就可以调⽤成员函数join()或try_join_for()/try_join_until()来阻塞等待线程执⾏结束。两者的区别如下:
- join()⼀直阻塞等待,直到线程结束。
- try_join_for()/try_join_until()阻塞等待⼀定的时间段后,⽆论线程是否结束它都返回。注意,它不会⼀直阻塞等待到指定的时间⻓度,如果在这段时间⾥线程运⾏结束,即使时间未到它也会返回。
void StudyThreadClass::Test()
{
for (size_t i = 0; i < 10000; i++) //初始化vector
{
_map[i] = "hello world";
}
for (size_t i = 0; i <10; i++) //创建线程对象
{
thread_vec.push_back(boost::thread(&StudyThreadClass::MutexStudy, this, i));
}
for (size_t i = 0; i <10; i++) //开线程
{
thread_vec[i].join();
}
}
3.分离线程
成员函数detach()将thread与线程执⾏体⼿动分离,此后thread对象不代表任何线程体, joinable()==false,从⽽失去对线程体的控制。因此当不需要再操作线程体时,我们就可以使⽤临时对象来启动线程,随即调⽤它的 detach( )。
4.线程辅助类thread_guard(包装thread)
thread库在C++标准之外提供thread_guard辅助类,可以让我们⽅便地控制 thread对象析构时的⾏为。thread_guard类可以更明确地表示我们使⽤线程的意图,默认动作是join(),⽽且由于它使⽤了RAII,所以它更加安全。
boost::thread _thread(&StudyThreadClass::MutexStudy, this, i));
boost::thread_guard<> _thread_guard(_thread);
5.RAII线程类scoped_thread
thread库⾥还有⼀个与thread的接⼝和功能完全⼀致的线程类scoped_thread,它可以使⽤模板参数定制析构的动作,默认是join()动作。 scoped_thread的命名和scoped_ptr⼀样清晰,它的⽤法结合了thread和thread_guard。
boost::scoped_thread<>(&StudyThreadClass::MutexStudy, this, 1);
6.中断线程
boost::thread有两个⾮C++标准的成员函数interrupt()和interruption_requested(),它们允许正在执⾏的线程被中断。
- interrupt():要求线程中断执⾏。
- interruption_requested():检查线程是否被要求中断。
被中断的线程会抛出⼀个thread_interrupted异常,它是⼀个空类,不是std::exception或 boost::exception的⼦类。thread_interrupted异常应该在线程执⾏函数⾥被捕获并处理,如果线程不处理这个异常,那么默认的动作是中断线。
void StudyThreadClass::Test()
{
for (size_t i = 0; i < 10000; i++) //初始化vector
{
_map[i] = "hello world";
}
for (size_t i = 0; i < 10; i++)
{
thread_vec.push_back(boost::thread(&StudyThreadClass::MutexStudy, this, i));
}
for (size_t i = 0; i < 10; i++)
{
thread_vec[i].interrupt(); //中断
thread_vec[i].join();
}
}
线程不是在任意时刻都可以被中断的。thread库预定义了若⼲个线程的中断点,只有当线程执⾏到中断点的时候才可能被中断,⼀个线程可以拥有任意多个中断点。 thread库的中断点共12个,它们都是函数调⽤:
- thread::join()
- thread::try_join_for()/try_join_until()
- condition_variable::wait()
- condition_variable::wait_for()/wait_until()
- condition_variable_any::wait()
- condition_variable_any::wait_for()/wait_until()
- this_thread::sleep_for()/sleep_until()
- this_thread::interruption_point()
在上述中断点中,前11个中断点都是某种形式的等待函数,表明线程在阻塞等待的时候可以被中 断。⽽最后⼀个位于⼦名字空间boost::this_thread的interruption_point()则是⼀个特殊的中 断点函数,它并不等待,只是起到⼀个“标签”的作⽤,表示线程执⾏到这个函数所在的语句就可以被中断。
默认情况下线程都是允许中断的,但thread库允许进⼀步控制线程的中断⾏为。 ⼦名字空间boost::this_thread⾥提供了⼀组函数和类来共同完成线程的中断启⽤和禁⽤:
- interruption_enabled()函数检测当前线程是否允许中断。
- interruption_requested()函数检测当前线程是否被要求中断。
- 类disable_interruption是⼀个RAII类型的对象,它构造时关闭线程的中断,析构时⾃动恢复线 程的中断状态。在它的⽣命期内线程始终是不可中断的,除⾮使⽤了restore_interruption对象。
- restore_interruption只能在disable_interruption的作⽤域内使⽤,它在构造时临时启⽤线程的 中断状态,在析构时⼜关闭线程的中断状态。
void StudyThreadClass::MutexStudy(int threadId)
{
try
{
while (_map.size() > 0) {
_mutex.lock();
std::string msg = boost::lexical_cast<std::string>(index);
_map[index] = msg;
std::cout << "线程id:" << threadId << "," << _map[index] << "," << _map.size() << std::endl;
_map.erase(index);
index++;
if (_map.size() < 9995)
{
boost::this_thread::interruption_point();
boost::this_thread::disable_interruption di;
if (_map.size() < 9993)
{
boost::this_thread::restore_interruption ri(di);
boost::this_thread::interruption_point();
}
}
_mutex.unlock();
}
}
catch (boost::thread_interrupted& e)//捕获线程中断异常
{
std::cout << "thread interrupted!" << std::endl;
}
}
7.线程组
thread库提供类thread_group⽤于管理⼀组线程,它就像⼀个“线程池”,它内部使⽤std::list< thread*>来容纳创建的thread对象。
成员函数create_thread()是⼀个⼯⼚函数,它可以创建thread对象并运⾏线程,同时可以加⼊内部的list。注意它不⽀持像thread构造函数那样传递函数参数的⽤法,所以通常有必要使⽤bind 或lambda来包装执⾏的函数,当然我们也可以在thread_group外部创建线程对象,使⽤add_thread()加⼊线程组。
如果不需要某个线程,remove_thread()可以删除list⾥的thread 对象。
成员函数is_this_thread_in()和is_thread_in()可以判断当前线程或某个线程是否属于本线程组。
join_all()和interrupt_all()⽤来操作list⾥的所有线程对象,等待或中断这些线程。
thread_group内部使⽤shared_mutex来保护线程list,所以它本身是线程安全的,在多线程环境⾥可以放⼼使⽤。
void StudyThreadClass::Test()
{
boost::thread_group _thread_group;
_thread_group.create_thread(std::bind(&StudyThreadClass::MutexStudy, this, 0));
_thread_group.create_thread(std::bind(&StudyThreadClass::MutexStudy, this, 1));
_thread_group.join_all();
}
8.call_once
为了保证初始化函数在多线程环境中被正确调⽤,thread库提供了仅调⽤⼀次的机制call_once, 使多个线程在操作函数时只能有⼀个线程成功执⾏,避免多次执⾏线程导致错误。
在使⽤call_once()函数时我们必须预先声明once_flag对象,⽽不能使⽤临时变量(即不能在传入参数中创建变量),否则会引发编译错误。
eg:在之前的test()函数中,创建线程之前我们会初始化vector,如果改为在MutexStudy()函数在初始化,并且只在一个线程中初始化成功,需要这样写:
void StudyThreadClass::Init()
{
std::cout << "init" << std::endl;
for (size_t i = 0; i < 10000; i++) //初始化vector
{
_map[i] = "hello world";
}
}
void StudyThreadClass::MutexStudy(int threadId)
{
static boost::once_flag flag;
boost::call_once(boost::bind(&StudyThreadClass::Init, this), flag);
//boost::call_once(flag, boost::bind(&StudyThreadClass::Init,this)); //如函数有参数,用这种形式
...
}
9.线程结束时执行操作
this_thread名字空间提供了⼀个at_thread_exit(func)函数,它允许“登记”⼀个线程在结束的时 候执⾏可调⽤物func,⽆论线程是否被中断。但如果线程被特定的操作系统API强⾏中⽌,或者程序调⽤标准C函数exit()(如main()函数 正常结束)、abort(),那么线程也会强制结束,at_thread_exit()登记的函数不会被调用。
三、条件变量
(之前的数据回流sdk中就用到了这个机制,只有业务请求数据回流向sdk传入回流的数据后,sdk内部的线程才会发起请求进行上传,所以需要等待数据)
条件变量是另⼀种⽤于等待的同步机制,它可以实现线程间的通信,它必须与互斥量配合使⽤, 等待另⼀个线程中某个事件发⽣(满⾜某个条件),然后线程才能继续执⾏。 thread库提供两种条件变量对象:condition_variable和condition_variable_any,⼀般情况下我们 应该使⽤condition_variable_any。
拥有条件变量的线程先锁定互斥量,然后循环检查某个条件,如果不满⾜条件,就调⽤条件变量 的成员函数wait()等待,直⾄满⾜条件。其他线程处理条件变量要求的条件:当条件满⾜时调 ⽤它的成员函数notify_one()或notify_all(),以通知⼀个或所有正在等待条件变量的线程停 ⽌等待,继续执⾏。
成员函数wait_for()和wait_until()的功能与wait()类似,但它们只等待⼀定的时间,如果 条件满⾜则结束等待,返回no_timeout值,如果条件未满⾜并且超时则返回timeout。
举个栗子:生产者-消费者
class StudyThreadClass
{
public:
void ConditionVariableAnyStudy();
private:
//存元素线程
void PutElement();
//条件变量
boost::condition_variable_any _condition_get;
//取元素线程
void GetElement(int threadId);
};
void StudyThreadClass::ConditionVariableAnyStudy()
{
boost::thread_group _group;
_group.create_thread(boost::bind(&StudyThreadClass::PutElement, this));//生产者
_group.create_thread(boost::bind(&StudyThreadClass::GetElement, this,0));//消费者
_group.create_thread(boost::bind(&StudyThreadClass::GetElement, this,1));//消费者
_group.join_all();
}
int count = 0;
void StudyThreadClass::PutElement()
{
while (true) {
for (size_t i = 0; i < 1; i++)
{
count++;
_queue.push(count);
std::cout << "存入元素:"<<count << std::endl;
}
if (!_queue.empty()) {
_condition_get.notify_all();
}
Sleep(20);
}
}
void StudyThreadClass::GetElement(int threadId)
{
while (true) {
_mutex.lock();
if (_queue.size() < 1) {
_condition_get.wait(_mutex);
}
if (!_queue.empty()) {
std::cout << threadId << " 取出元素:" << _queue.front() << std::endl;
_queue.pop();
}
_mutex.unlock();
Sleep(20);
}
}
四、异步操作线程返回值获取
1.future
很多情况下,线程不仅要执⾏⼀些⼯作,还可能会返回⼀些计算结果。thread库使⽤future范式提 供了异步操作线程返回值的⽅法,因为这个返回值在线程开始执⾏时是不可⽤的,该返回值是⼀ 个“未来”的“期待值”,所以它被称为future(期货)。
future的成员函数wait()的⾏为类似thread.join(),它可以阻塞等待线程的执⾏,直⾄获得 future值。
wait_for()/wait_until()增加了超时的功能,返回future_status枚举值表示是否执⾏ 成功。
成员函数get()⽤来获得future值,它会默认调⽤wait()等待线程计算完毕。get()只能被调 ⽤⼀次,在获取值后再调⽤get()的⾏为是未定义的——通常会抛出异常。
成员函数valid()/is_ready()、has_value()、has_exception()分别⽤来测试future是否 可⽤(后三个⾮C++标准)、是否有值、是否发⽣了异常,它们可以在wait()之后检查future的 状态,避免发⽣错误。
future还有两个模板特化形式:future<T&>和future<void>,对于后⼀种形式⽽⾔,成员函数 get()将返回void值,等价于wait()。
2.async()函数
async()函数⽤于产⽣future对象,它异步启动⼀个线程运⾏函数,返回future对象,这样随后我们就可以利⽤future获取计算结果。
async()函数的⽤法很像thread,它们都可以启动⼀个线程,但调⽤async()得到的是future 对象⽽不是thread对象,因此async()更关⼼函数的计算结果⽽不是过程。当然,我们完全可以忽略async()的返回值,把它简单地当作⼀个线程启动器。
策略枚举类launch可⽤于定制async()启动线程的时机:
- none:⽆意义,不能使⽤这个枚举值,否则会导致运⾏错误。
- async:要求⽴即启动⼀个线程执⾏函数。
- deferred:只有当显式调⽤future的get()/wait()时才会启动线程。
- any:async或deferred,不确定。
int StudyThreadClass::fab(int a)
{
if (a == 0 || a == 1)
{
return 1;
}
return fab(a - 1) + fab(a - 2);
}
void StudyThreadClass::AsyncStudy()
{
auto result_5 = boost::async(boost::bind(&StudyThreadClass::fab, this, 5));
auto result_7 = boost::async(boost::launch::async, boost::bind(&StudyThreadClass::fab, this, 7));
std::cout << result_7.get() << std::endl;
std::cout << result_5.get() << std::endl;
if (result_5.valid())
{
std::cout << result_5.get() << std::endl;
}
else {
std::cout << "result_5 not valid." << std::endl;
}
}
//结果:
21
8
result_5 not valid.
3. shared_future
future曾在thread的早期版本⾥的名字是unique_future,这是因为它的结果只能使⽤get()获取 ⼀次,这个限制使它不能被多个线程并发访问,不够⽅便。 shared_future是future的增强版本,它与future类似,但它可以线程安全地多次调⽤get()获取计算结果。
shared_future的接⼝与future相似,它们的区别仅在于get()的多线程访问性。 async()函数可以返回shared_future对象,但需要我们明确地声明类型。 future的share()函数也可以产⽣shared_future对象,这样就可以利⽤auto来⾃动推导类型,避免了⼿写的麻烦。
将上述代码改为:
auto result_5 = boost::async(boost::bind(&StudyThreadClass::fab, this, 5)).share();
//结果
21
8
8