• Item 38: Be aware of varying thread handle destructor behavior.


    Item 38: Be aware of varying thread handle destructor behavior.

    Item 37 介绍了 std::thread 对应一个底层的系统执行线程,一个非 deferred 任务的 future(这里包括 std::futurestd::shared_future,下同)也对应一个底层的系统执行线程。一个 joinable 的 std::thread 对象析构时会导致程序终止,因为隐式的 join() 和隐式的 detach() 可能会更加糟糕。但是,future析构函数可能是隐式地执行 detach(),也可能是隐式地执行 join(),或者二者皆不是。本 Item 将和大家探讨下这个问题。

    直观地观察,被调用者(callee)和调用者(caller)之间有一个通信通道(channel),callee 异步执行完成后,将结果写入(通常通过 std::promise 对象)这个通道,caller 通过 future 读取结果。类似下面这个模型:
    在这里插入图片描述
    但是,这个模型有点问题:callee 的结果存储在哪里?首先,不能存储在 callee 的 std::promise 对象中,因为 caller 在调用 futureget 方法之前,callee 可能已经结束了,callee 的局部变量 std::promise 已经销毁了。再者,callee 的结果也不能存储在 caller 的 std::future 中,因为 std::future 可以用来创建 std::shared_future ,那么这个结果就需要被拷贝多次,不是所有结果的类型都是可以拷贝的。其实 callee 的结果是被存储在独立于 caller 和 callee 之外的特殊位置,被成为共享状态(shared state)的位置。模型如下:
    在这里插入图片描述
    由于这个共享状态的存在, future 的析构函数的行为则与这个共享状态关联的 future 决定:

    • std::async 发起的非 deferred 的任务的返回的 future 对象,并且它是最后一个引用共享状态的,那么它的析构会一直阻塞到任务完成,也就是隐式执行 join()
    • 其他的 future 只是简单的销毁。对于异步任务,类似隐式执行 detach(),对于 deferred 策略的任务则不再运行。

    反过来看,future 会隐式执行 join() 需要满足下面 3 个条件:

    • future 是由 std::async 创建产生,并且引用共享状态。
    • std::async 指定的任务策略是 std::launch::async
    • future 是最后一个引用共享状态的对象。

    future 的 API 没有提供它是否由 std::async 产生并指向共享状态,因此,对于任意的 future,我们无法知道它的析构函数会不会阻塞到任务完成。

    // this container might block in its dtor, because one or more
    // contained futures could refer to a shared state for a non-
    // deferred task launched via std::async
    std::vector<std::future<void>> futs;   // see Item 39 for info
                                           // on std::future
    class Widget {                         // Widget objects might
    public:                                // block in their dtors
      ...
    private:
      std::shared_future<double> fut;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果你知道给定的 future 不满足上述 3 个条件,那么其析构函数就不会阻塞住。例如,future 来自于 std::packaged_task

    int calcValue();               // func to run
    
    std::packaged_task<int()>      // wrap calcValue so it
      pt(calcValue);               // can run asynchronously
      
    auto fut = pt.get_future();    // get future for pt
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    一般地, std::packaged_task pt 需要运行在一个 std::thread 上, std::packaged_taskfuture 是的析构是结束程序,还是执行 join异或执行 detach,交给了对应的 std::thread 接下来的行为了:

    {                                  // begin block
      std::packaged_task<int()>
        pt(calcValue);
        
      auto fut = pt.get_future();
      
      std::thread t(std::move(pt));   // std::packaged_task 不可拷贝
      
      ...                              // see below
    }                                  // end block 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    “…” 就是接下来操作 t 的代码,可能如下:

    • t 什么也没做(没有 join 也没有 detach)。在 block 结束时, t 是 joinable 的,这会导致程序终止。
    • t 执行 join 。这个时候, fut 的析构函数没有必要再执行 join
    • t 执行 detach 。这个时候, fut 的析构函数没有必要再执行 detach

    下面给一个测例作为验证:

     #include 
     #include 
     #include 
     #include 
     
     using namespace std::chrono_literals;
     
     void func() {
         std::this_thread::sleep_for(3s);
     }
     
     int main() {
         {   
             std::packaged_task<void()> pt(func);
             auto fut = pt.get_future();
             std::thread t(std::move(pt));
             // t.join();
             // t.detach();
         }   
         std::cout << "hello!" << std::endl;
         return 0;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    执行结果如下:

    terminate called without an active exception
    Aborted (core dumped)
    
    • 1
    • 2

    join 或者 detach 部分代码打开,则不会产生上述问题。

    至此,本文结束。

    参考:

  • 相关阅读:
    【Leetcode刷题Python】416. 分割等和子集
    RabbitMQ备份交换机与优先级队列
    高仿英雄联盟游戏网页制作作业 英雄联盟LOL游戏HTML网页设计模板 简单学生网页设计 静态HTML CSS网站制作成品
    部署zabbix5.4
    SpringCloud 客户端负载均衡:Ribbon
    ip mac地址匿名化
    Spring源码级笔记(二)
    我们又组织了一次欧洲最大开源社区活动,Hugging Face 博客欢迎社区成员发帖、Hugging Chat 功能更新!...
    Python gRPC 基础教程
    Java21 LTS版本
  • 原文地址:https://blog.csdn.net/Dong_HFUT/article/details/126435546