• 线程的基本操作(三)


    转移所有权

    假设通过新线程返回的所有权去调用一个需要后台启动线程的函数,并需要在函数中转移线程的所有权。这些操作都要等待线程结束才能进行,并且需要线程的所有权能够进行转移。

    这就是将移动操作引入std::thread的原因,C++标准库中有很多资源占有(resource-owning)类型,比如std::ifstreamstd::unique_ptr还有std::thread都是可移动,但不可复制。这说明执行线程的所有权可以在std::thread实例中移动,下面将展示一个例子。例子中,创建了两个执行线程,并在std::thread实例之间t1,t2和t3t1,t2和t3转移所有权:

    1. void some_function();
    2. void some_other_function();
    3. std::thread t1(some_function); // 1
    4. std::thread t2 = std::move(t1); // 2
    5. t1 = std::thread(some_other_function); // 3
    6. std::thread t3; // 4
    7. t3 = std::move(t2); // 5
    8. t1 = std::move(t3); // 6 赋值操作将使程序崩溃

    首先,新线程与t1相关联①。当显式使用std::move()创建t2后②,t1的所有权就转移给了t2。之后,t1和执行线程已经没有关联了,执行some_function的函数线程与t2关联。

    然后,临时std::thread对象相关的线程启动了③。为什么不显式调用std::move()转移所有权呢?因为,所有者是一个临时对象——移动操作将会隐式的调用。

    t3使用默认构造方式创建④,没有与任何线程进行关联。调用std::move()将t2关联线程的所有权转移到t3中⑤。因为t2是一个命名对象,需要显式的调用std::move()。移动操作⑤完成后,t1与执行some_other_function的线程相关联,t2与任何线程都无关联,t3与执行some_function的线程相关联。

    最后一个移动操作,将some_function线程的所有权转移⑥给t1。不过,t1已经有了一个关联的线程执行some_other_function的线程,所以这里系统直接调用std::terminate()终止程序继续运行。这样做(不抛出异常,std::terminate()noexcept函数)是为了保证与std::thread的析构函数的行为一致。需要在线程对象析构前,显式的等待线程完成,或者分离它,进行赋值时也需要满足这些条件(说明:不能通过赋新值给std::thread对象的方式来"丢弃"一个线程)

    std::thread支持移动,线程的所有权可以在函数外进行转移,就如下面程序一样。

    函数返回std::thread对象

    1. std::thread f()
    2. {
    3. void some_function();
    4. return std::thread(some_function);
    5. }
    6. std::thread g()
    7. {
    8. void some_other_function(int);
    9. std::thread t(some_other_function,42);
    10. return t;
    11. }

    当所有权可以在函数内部传递,就允许std::thread实例作为参数进行传递,代码如下:

    1. void f(std::thread t);
    2. void g()
    3. {
    4. void some_function();
    5. f(std::thread(some_function));
    6. std::thread t(some_function);
    7. f(std::move(t));
    8. }

    std::thread支持移动可以创建thread_guard类的实例,并且拥有线程所有权。当引用thread_guard对象所持有的线程时,移动操作就可以避免很多不必要的麻烦。当某个对象转移了线程的所有权,就不能对线程进行汇入或分离。为了确保线程在程序退出前完成,定义了scoped_thread类。现在,我们来看一下这个类型:

    1. class scoped_thread
    2. {
    3. std::thread t;
    4. public:
    5. explicit scoped_thread(std::thread t_) // 1
    6. :t(std::move(t_))
    7. {
    8. if (!t.joinable()) // 2
    9. throw std::logic_error("No thread");
    10. }
    11. ~scoped_thread()
    12. {
    13. t.join(); // 3
    14. }
    15. scoped_thread(scoped_thread const&) = delete;
    16. scoped_thread& operator=(scoped_thread const&) = delete;
    17. };
    18. struct func;
    19. void f()
    20. {
    21. int some_local_state;
    22. scoped_thread t(std::thread{ func(some_local_state )}); // 4
    23. do_something_in_current_thread();
    24. } // 5

    该代码中,新线程会直接传递到scoped_thread中④,而非创建一个独立变量。当主线程到达f()末尾时⑤,scoped_thread对象就会销毁,然后在析构函数中完成汇入③。thread_guard类,需要在析构中检查线程是否“可汇入”。这里把检查放在了构造函数中②,并且当线程不可汇入时抛出异常。

    C++17标准给出一个建议,就是添加一个joining_thread的类型,这个类型与std::thread类似,不同是的添加了析构函数,就类似于scoped_thread。委员会成员们对此并没有达成统一共识,所以这个类没有添加入C++17标准中(C++20仍旧对这种方式进行探讨,不过名称为std::jthread),这个类实现起来也不是很困难。

    1. class joining_thread
    2. {
    3. std::thread t;
    4. public:
    5. joining_thread() noexcept = default;
    6. template<typename Callable, typename ...Args>
    7. explicit joining_thread(Callable&& func, Args&& ...args):
    8. t(std::forward(func), std::forward(args)...)
    9. {}
    10. explicit joining_thread(std::thread t_) noexcept
    11. :t(std::move(t_))
    12. {}
    13. joining_thread(joining_thread&& other) noexcept
    14. :t(std::move(other.t))
    15. {}
    16. joining_thread& operator=(joining_thread&& other) noexcept
    17. {
    18. if (joinable())
    19. join();
    20. t = std::move(other.t);
    21. return *this;
    22. }
    23. joining_thread& operator=(std::thread other) noexcept
    24. {
    25. if (joinable())
    26. join();
    27. t = std::move(other);
    28. return *this;
    29. }
    30. void swap(joining_thread& other) noexcept
    31. {
    32. t.swap(other.t);
    33. }
    34. std::thread::id get_id() const noexcept {
    35. return t.get_id();
    36. }
    37. ~joining_thread()
    38. {
    39. if (joinable())
    40. join();
    41. }
    42. bool joinable() const noexcept
    43. {
    44. return t.joinable();
    45. }
    46. void join()
    47. {
    48. t.join();
    49. }
    50. void detach()
    51. {
    52. t.detach();
    53. }
    54. std::thread& as_thread() noexcept
    55. {
    56. return t;
    57. }
    58. const std::thread& as_thread() const noexcept
    59. {
    60. return t;
    61. }
    62. };

    std::thread中对移动语义的支持,也适用于使用std::thread移动敏感(move-aware)容器(比如,std::vector<>)。了解这些后,就可以量产了一些线程,并且等待它们结束,代码如下所示。

    量产线程,代码如下:

    1. std::vector threads;
    2. for (unsigned i = 0; i < 10; ++i)
    3. {
    4. threads.emplace_back(hello);
    5. }
    6. for (auto& entry : threads)
    7. entry.join();

    std::thread放入std::vector是向线程自动化管理迈出的第一步:并非为这些线程创建独立的变量,而是把它们当做一个组。创建一组线程数量在运行时确定。

  • 相关阅读:
    Java与Scala编译的简单对比
    RK3399平台开发系列讲解(PCI/PCI-E)5.55、PCIE RC枚举EP过程
    从0开始学习JavaScript--JavaScript使用Promise
    矩阵论复习提纲
    企业宣传为何要重视领军人物包装?领军人物对企业营销的价值和作用分析
    Virtualbox固定存储硬盘转换为动态存储硬盘
    JavaScript中的事件捕获(event capturing)和事件冒泡(event bubbling)
    每个后端都应该了解的OpenResty入门以及网关安全实战
    宝塔安装的TENGINE(NGINX)添加FAIR模块实现自动负载均衡
    区间重叠问题
  • 原文地址:https://blog.csdn.net/u012069234/article/details/126944665