深圳服装网站建设,ui网页设计课程总结,wordpress装多站点,临沂网站建设咨询条款37解释过#xff0c;可联结的线程对应着一个底层系统执行线程#xff0c;未推迟任务#xff08;参见条款36#xff09;的期值和系统线程有类似关系。这么一来#xff0c;std::thread型别对象和期值对象都可以视作系统线程的句柄。
从这个视角来看#xff0c;std::th…条款37解释过可联结的线程对应着一个底层系统执行线程未推迟任务参见条款36的期值和系统线程有类似关系。这么一来std::thread型别对象和期值对象都可以视作系统线程的句柄。
从这个视角来看std::thread对象和期值对象的析构函数表现出如此不同的行为值得深思。正如条款37所提及的针对可联结的std::thread型别对象实施析构会导致程序终止因为另外两个显而易见的选择隐式join和隐式detach都被认为是更糟糕的选择。而期待的析构函数呢有时候行为像是执行了一次隐式join,有时候行为像是执行了一次隐式detash有时候行为像是二者都没有执行。它从不会导致程序终止。这套线程句柄行为的大杂烩值得我们仔细品鉴一番。
我们的观察从这里开始期值位于信道的一端被调方把结果通过该信道传输给调用方。被调方通常以异步方式运行把其计算所得的结果写入信道通常经由一个std::promise型别对象而调用方则使用一个期值来读取该结果。你可以把这个过程想成下图虚线箭头代表着从被调方向调用方的信息流 但被调方的结果要存储在哪里呢在调用方唤起对应期值的get之前被调方可能已经执行完毕因此结果不会存储在被调用方的std::promise型别对象里。那个对象对于被调方来说就是个局部量在被调方结束后会实施析构。
该结果也不能存储在调用方的期值中因为出于其他种种原因可能会从std::future型别对象出发创建std::shared_future型别对象因此把被调方结果的所有权从std::future型别对象转移至std::shared_future型别对象而后者可能会在原始的std::future析构之后复制多次。如果被调方的结果型别不都是可复制的即只移型别而该结果至少生存期要延至和最后一个指涉到它的期值一样长。这么多个对应同一结果的期值中的哪一个应该包含该结果呢
既然与被调方相关联的对象和与调用方相关联的对象都不适合作为被调方结果的存储之所那就只能将该结果存储在位于两者外部的某个位置。这个位置称为共享状态。共享状态通常使用堆上的对象来表示但是其型别、接口和实现标准皆未指定。标准库作者可以自由地用他们喜好的方法去实现共享状态。
我们可以把调用方、被调用方和共享状态之间的关系使用下图来表示虚线箭头仍然表示着信息流 共享状态的存在很重要因为期值析构函数的行为这也是本条款的议题是由与其关联的共享状态决定的。具体来说就是
指涉到经由std::async启动的未推迟任务的共享状态的最后一个期值会保持阻塞直至该任务结束。本质上这样一个期值的析构函数是对底层异步执行任务的线程实施了一次隐式join.其他所有期值对象的析构函数只仅仅将期值对象析构就结束了。对于底层异步运行的任务这样做类似于对线程实施了一次隐式detach.对于那些被推迟任务而言如果这一期值是最后一个也就意味着被推迟的任务将不会有机会运行了。
这些规则听上去复杂其实不然我们真正需要关心的是一个平凡的常规行为外加一个不甚常见的例外而已。常规行为是指期值的析构函数仅会析构期值对象。就这样。它不会针对任何东西实施join,也不会从任何东西实施detach也不会运行任何东西。它仅会析构期值的成员变量好吧实际上它还多做了一件事。它针对共享状态里的引用计数实施了一次自减。该共享状态由指涉到它的期值和被调方的std::promise共同操作。该引用计数使得库能知道何时可以析构共享状态。关于引用计数的一般材料参见条款19。
而相对于正常行为的那个例外只有在期值满足以下全部条件时才会发挥作用
期值所指涉的共享状态是由于调用了std::async才创建的该任务的启动策略是std::launch::async。这既可能是运行时系统的选择也可能是在调用std::async时指定的。该期值是指涉到该共享状态的最后一个期值对于std::future型别对象而言这一点总是成立。而对于std::shared_future型别对象而言在析构时如果不是最后一个指涉到共享状态的期值则它会遵循常规行为准则即仅析构其成员变量
只有当所有条件都满足期值的析构函数才会表现出特别行为。而行为的具体表现为阻塞直到异步运行的任务结束。从效果来看这相当于针对正在运行的std::async所创建的任务的线程实施了一次隐式join。
经常会有人把这个例外和常规期望析构函数行为的差异说成是“来自std::async的期值会在其析构函数里被阻塞。”如果只是最粗略的近似这种说法也不为错但有时候你需要比最粗略的近似了解得更深一步。而现在你已经知道了全面的真相。
抑或你的疑问又并不同可能会是“为什么要为从std::async出发启动的非推迟任务相关联的共享状态专门制定一条规则”问得合理。根据我所知道的标准委员会想要避免隐式detach相关的问题参见条款37但是他们又不想简单粗暴地让程序终止了事他们针对可联结线程就是这样做的参见条款37所以妥协结果就是实施一次隐式join.这个决定并非没有争议委员会也曾认真讨论过要在C14中舍弃这样的行为但是最后没有做出改变所以期值析构函数的行为在C11和C14中是保持了一致的。
期值的API没有提供任何方法判断其指涉的共享状态是否诞生于std::async的调用所以给定任意期值对象的前提下它不可能知道自己是否会在析构函数中阻塞到异步任务执行结束。这个事实暗示着一些意味深长的推论
//该容器的析构函数可能会在其析构函数中阻塞
//因为它所持有的期值中可能会有一个或多个
//指涉到经由std::async启动未推迟任务所产生的共享状态
std::vectorstd::futurevoid futs; //关于std::futurevoid参见条款39class Widget{
public: //Widget型别对象可能会在其析构函数中阻塞...private:std::shared_futruedouble fut;
};
当然如果有办法判定给定的期值不满足触发特殊析构行为的条件例如通过分析程序逻辑即可断定该期值不会阻塞在其析构函数中。例如只有因std::async调用而出现的共享状态才够格去展示特别行为但是还有其它方法可以创建出共享状态。其中一个方法就是运用std::packaged_task,std::packaged_task型别对象会准备一个函数或其他可调用的对象以供异步执行手法是将它加上一层包装把其结果置入共享状态。而指涉到该共享状态的期值则可以经由std::packaged_task的get_future函数得到
int calcValue(); //待运行的函数std::packaged_taskint() pt(calcValue); //给calcValue加上包装使之能以异步方式运行auto fut pt.get_future(); //取得pt的期值
此时此刻我们已知期值对象fut没有指涉到由std::async调用产生的共享状态所以它的析构函数将表现出常规行为。
std::packaged_task型别对象pt一经创建就会运行在线程之上它也可以经由std::async的调用而运行但是如果你要用std::async运行任务就没有很好的理由再去创建什么std::packaged_task型别对象因为std::async能够在调度任务执行之前就做到std::packaged_task能够做到的任何事情。
std::packaged_task不能复制所以欲将pt传递给std::thread的构造函数就一定要将它强制转型有右值经由std::move,参加条款23
std::thread t(std::move(pt)); //在t之上运行pt
此例让我们能够隐约看出一些期值的常规析构行为但如果把这些语句都放在同一代码块中就可以看得更加清楚
{ //代码块开始std::packaged_taskint() pt(calcValue);auto fut pt.get_future();std::thread t(std::move(pt));... //见下} //代码块结束
这里最值得探讨的代码是“...”部分它位于t创建之后、代码块结束之前。值得探讨的是在‘...’中t的命运如何。基本存在三种可能
未对t实施任何操作。在这种情况下t在作用域结束点是可联结的而这将导致程序终止针对t实施了join.在此情况下fut无须在析构函数中阻塞因为在调用的代码已经有过join针对t实施了detach。在此情况下fut无须在任何析构函数中实施detach,因为在调用的代码已经做过了这件事了
换句话说当你的期值所对应的共享状态是由std::packaged_task产生的则通常无需采用特别的析构策略。因为关于是终止、联结还是分离的决定会由操纵std::thread的代码作出而std::packaged_task通常就运行在该线程之上。
要点速记
期值的析构函数在常规情况下仅会析构期值的成员变量指涉到经由std::async启动的未推迟任务的共享状态的最后一个期值会保持阻塞直至该任务结束