catalog
std::async
string fetchData_fromDb0( string _info){
this_thread::sleep_for( chrono::seconds( x);
return "data from db0";
}
string fetchData_fromDb1( string _info){
this_thread::sleep_for( chrono::seconds( x));
return "data from db1";
}
假如我们的主线程是: string db0 = fetchData_fromDb0( "");
string db1 = fetchData_fromDb1( "");
这自然会消耗: x + x 的时间
我们可以并行的运行这2个函数;
(一种方式是: 我们手动的 创建一个线程) (一种方式是: 使用async函数)
我们这里先介绍下: (async方式)
future< string> db0_future = async( launch::async, fetchData_fromDb0, "info0");
' 他会自动创建一个线程(比如称为0), 此时, fetch0函数就开始执行了 '
future< string> db1_future = async( launch::async, fetchData_fromDb1, "info1");
' 他会自动创建一个线程(比如称为1), 此时, fetch1函数就开始执行了 '
' 此时的状态是: 子线程0 和 1 和 主线程, 3个 在并行运行cpu '
string db0_data = db0_future.get();
' 一旦调用get(), 主线程会卡死, 直到子线程0结束; 即此时状态是: 主线程会卡住, 即子线程0和1, 共2个 在并行运行cpu '
' 此时, 子线程0结束了, 即主线程 和 子线程1, 共2个 在并行运行cpu '
string db1_data = db1_future.get();
' 此时, 主线程再次卡死, 直到 子线程1结束; 即此时, 只有子线程1, 1个 在运行cpu '
' 此时, 只有主线程运行; '
cout << db0_data << ", "<< db1_data;
此时, 我们发现, 原先是需要(x + x)的时间, 现在是: (x)的时间 可以通过, clock() 测试下
std::launch
从上面的使用中, 我们发现: future< string> db0_future = async( launch::async, fetchData_fromDb0, "info0")
解释下: future< 函数func的返回值> fut = async( launch::async/deferred, func, 函数func的参数)
第一参数是: std::launch 的枚举
async
: 表示, 执行async这个函数时, 就立刻 开始异步(创建新线程)的 执行func函数deferred
: 执行async函数时, 不会立刻执行func函数;
直到你执行fut.get()
时, 会以非异步(不会创建新线程, 而是在主线程)执行func函数; (他还是单线程模式!!!)
这个deferred, 看似很没有呀… 一会讲他.future< string> fut = async( launch::deferred, addressof( func)); aaa; string ret = fut.get(); ' 以上写法, 完全等价于: ' aaa; string ret = func();
future
对于一个空future对象: future< ..> fut;
, 他的valid()
是false!!!
就意味着, 你调用: fut.get() 或 wait_for()
, 都会抛出异常!!!
一般, future 我们不会自己创建他, 都是系统创建的, 然后我们copy对象
future< int> fut = xx.get_future();
xx
是系统函数
future.wait_for()
需求: 一边执行func函数, 一边 每隔1s 提示用户(正在执行中…); 等执行完函数, 等待用户录入信息
可以知道, 这肯定是(多线程环境); 即为: async( launch::async)
方式; 而不是launch::deferred
因为他是单线程;
future 顾名思义, 即未来;
他有几个作用:
- 子线程执行完后, 会将返回值 存储到 future.get()里面
- future里有一个
future_status
的枚举, 他表示: 当前子线程的状态-
future_status::ready
: 子线程已经执行结束, 返回值在future.get()里;关于
fut.get()
, 多讲一点:- (如果1, 子线程已经结束, 则立刻将子线程的返回值 通过get()返回)
- (否则2, 子线程正在执行, 则将
调用者线程
(即,fut
所处在的线程) 给卡死, 直到 子线程执行完成;
这是很当然的, 因为, 你调用get()
, 就是要获取 (子线程)的返回值, 而他现在正在执行,
当然需要将调用者线程给卡死, 等待他的结果出来
-
future_status::timeout
: 子线程还正在执行 -
future_status::deferred
: 这个只针对launch::deferred
的async调用; 对于launch::async
方式, 不会有这个状态;
-
future_status st = future.wait_for( 1s);
- 让当前线程(这行代码所在线程), 卡死1s后, 将子线程的状态 给返回
看到wait_for
, 就应该知道, 肯定有一个wait_until
;
这两个函数, 不仅在这里, 但很多标准库里都有; 而且, 这两个函数, 是 完全一样的!!! 两者可以完全等效的替换
在任何场景下, wait_for( x) == wait_unitl( current_time + x)
future< string> fut = async( launch::async, addressof( func)); ' func就开始 在(新子线程)里, 执行开始了 '
chrono::milliseconds span( 1000);
while( true){
future_status st = fut.wait_for( span);
if( st == future_status::ready){
cout << "函数func执行结束" << endl;
cout << "返回值为: " << fut.get() << endl;
break;
}
else if( st == future_status::timeout){
cout << "函数func, 正在执行中..." << endl;
}
else if( st == future_status::deferred){ ' 这里, 因为是launch::async方式, 不会出现这种情况 '
cout << "函数func, 尚未开始执行" << endl;
}
}
以上代码的现象是:
- func 和 主线程, 并行执行cpu
- 每隔1s, 主线程 询问下 子线程的状态; (如果1, 正在执行, 则输出
...
) (如果2, 执行结束, 则break
) 不会有第3种情况;
valid
future_status wait_for( ..){
if( false == this->valid()){
throw future_error();
}
...
}
对于一个空future对象: future< ..> fut;
, 他的valid()
是false!!!
就意味着, 你调用: fut.get() 或 wait_for()
, 都会抛出异常!!!
一般, future 我们不会自己创建他, 都是系统创建的, 然后我们copy对象
future< int> fut = xx.get_future();
xx
是系统函数
future.valid, future.get(), future.wait()
对于launch::async
方式, 只要没有调用过fut.get()
, 则fut.valid() == true
, 表示, 该future是有效的
一旦调用过 fut.get()
, 则, fut.valid() == false
future.get() 只能调用1次!!! 不能调用多次!!! 否则, 会抛出 future_error
异常
T future::get(){
if( this->valid() == false){
throw future_error();
}
this->wait();
return ret_value;
}
future::wati()
的作用是: (只要子线程没有结束, 则让调用者线程, 一直卡死)
内部成员变量: 共享状态/数据
从上面的: future< string> fut = async( launch::async, addressof( func));
, 可以知道几个信息:
async
函数里, 有一个future
对象(比如命名为sub_fut
) 他以返回值的方式, 传递给了 我们当前主线程的fut对象;- 在当前主线程 和 新子线程, 两者并行运行的期间,
fut
里的状态 (子线程正在执行 或 执行结束), 会动态更新
而, 我们的 (当前主线程) 就没有修改过这个fut
对象, 这说明, future里, 肯定有 (指针/引用)方式 来指向 (子线程)里的 状态;
State * ptr_state; ' 指向, 子线程里的 状态 '
Result & ref_res; ' 指向, 子线程里的 返回值 '
这个两个变量, 都是: (不同线程间, 使用 同一个内存) 的情况;
promise
我们从上面的future 知道, 我们不会自己创建一个future对象; 都是直接: auto fut = xx.get_future()
即, future都是 系统给我们维护的;
那么, 哪些类/函数里, 会自动给我们创建future呢?
promise类, 他里面就有个future对象;
promise< T> pro;
此时, pro.get_future()
他的状态是:
valid 有效的
(我们知道, 我们自己创建的future都是invalid的, 只有系统里的future 才会是valid)`timeout 表示: future里的数据(get()), 还没有设置
promise"承诺", 承诺 在(某个时间) 会调用set_value 或 set_value_at_thread_exit
一旦set_value
后, future的状态 就更新为: ready
, 表示: future里有数据了
获取线程返回值
我们之前介绍了, 通过async函数
来获取 , 子线程的返回值;
再介绍一种:
void func( promise< string> & pro){
...
pro.set_value( "thread return date");
' 此时, pro.get_future()的状态会变为{从timeout 变成 ready}; 这个string返回值, 也会放到future().get()里 '
' 一般会使用: pro.set_value_at_thread_exit( ..); 因为, 上面的set_value() 回立刻改变 pro.get_future() '
' 即, 外界立刻就得到了返回值; 因为一般概念下, 得到函数的返回值, 就意味着: 这个函数已经执行结束 '
' 而在这里, 如果使用set_value, 外界得到{返回值}, 会误以为: 这个函数(即子线程) 已经执行完毕; '
' 此时不然!!! set_value的下面, 还有代码, 此时函数并未结束 子线程也并未结束; '
' 所以, 使用set_value_at_thread_exit更好!! 等这个函数执行完毕后, 才会更新future里的内容 '
}
int main(){
promise< string> pro;
thread th( addressof( func), ref( pro));
future< string> fut = pro.get_future();
while( fut.wait_for( chrono::seconds( 1)) != future_status::ready){ ' 等于: timeout (表示: future等待赋值) '
cout << "thread is working..." << endl;
}
cout << "main-thread receive the sub-thread return data: " << fut.get() << endl;
th.join(); ' 只要是一个线程, 就要写: 要么join, 要么detach '
}
set_value, set_value_at_thread_exit
set_value
函数, 只能调用1次!!!
- 因为, 初始时
promise
里的future
对象, 他的status状态是:timeout
(表示: future里的数据, 还没有设置值) - 而当你
set_value
后, future对象 有值了, 他的status状态是:ready
(表示: future里有数据了) - future, 只有1个返回值, 只能调用1次
set_value
get_future
get_future()
函数, 只能调用1次!!!
- 否则会抛出异常:
Future already retrieved
, 这个对象 只能索引/获取 1次,
因为我们知道, future里 有很多共享数据 (是跨线程的共享)比如: 状态, 返回值
而且, future的 赋值/拷贝 都是delete掉了的; 只有(移动) 赋值/拷贝, 是可以的;
因为有(跨线程的)共享数据, 只允许有1个 future对象, 这样设计很安全