C++11线程的生命周期

前言

父母必须照管好孩子。线程也一样,父线程对子线程生命周期有很大的影响。
以下程序启动一个显示其ID的线程。

// threadForgetJoin.cpp

#include <iostream>
#include <thread>

int main()
{
    std::thread t([]
    {
        std::cout << std::this_thread::get_id() << std::endl;
    });
}

程序运行会导致意外结果:
这里写图片描述
这是啥原因呢?


加入和分离

创建的线程t的生命周期随着可调用它的单元的结束而结束了。
线程创建者有两种选择:

1、它等待,直到它的孩子完成(t.join())。
2、它把它的孩子t分离出去了(t.detach())。

在上面程序中,什么操作都没得,不管是t.join()还是t.detach(),所以这个线程析构时抛出std::terminate异常,因此,程序终止。
这就是原因,实际运行中意外终止。
这个问题的解决方案很简单。通过调用t.join(),程序可以正常运行。

// threadWithJoin.cpp

#include <iostream>
#include <thread>

int main() 
{
    std::thread t([] 
    {
        std::cout << std::this_thread::get_id() << std::endl; 
    });
    t.join();
}

这里写图片描述

分离的挑战

当然,您可以在上面的程序中使用t.detach()而不是t.join()。
线程t不再可连接,它的析构函数不会调用std::terminate。看起来很糟糕,因为现在程序行为是未定义的,因为没有确保对象std::cout的生命周期。
该程序的执行有点奇怪:
这里写图片描述
我将在下一篇文章中详细阐述这个问题。


move线程

到现在为止,这还是容易理解的。但接下来要注意了。。。。
想要复制一个线程(复制语义)是不可能的,你只能move(移动语义)它。
如果一个线程被move,想以正确的方式处理它的生命周期要困难得多。

// threadMoved.cpp

#include <iostream> 
#include <thread> 
#include <utility>

int main() 
{
    std::thread t([] 
    {
        std::cout << std::this_thread::get_id(); 
    });
    std::thread t2([] 
    {
        std::cout << std::this_thread::get_id(); 
    });

    t = std::move(t2);
    t.join();
    t2.join();
}

两个线程 t1和t2都只是干了个简单的活:打印它们的ID。
除此之外,Thread t2将被移动到t:t = std::move(t2);。
最后,主线程照顾它的孩子并等它们执行完毕。
可是等等。。。。。这是发生啥了?????
这里写图片描述
windows平台:
这里写图片描述

出了啥问题?有两个问题:

1、通过move(获取其所有权)线程t2,t获得一个新的可调用单元,并且将调用其析构函数。所以t的析构函数调用std::terminate,因为它仍然是可连接的。
2、但是线程t2没有相关的可调用单元。在没有可调用单元的线程上调用join会导致异常std::system_error。


修复这两个错误:

// threadMovedFixed.cpp

#include <iostream> 
#include <thread> 
#include <utility>

int main() 
{
    std::thread t([]
    {
        std::cout << std::this_thread::get_id() << std::endl;
    });
    std::thread t2([]
    {
        std::cout << std::this_thread::get_id() << std::endl;
    });

    t.join();
    t = std::move(t2);
    t.join();

    std::cout << "\n";
    std::cout << std::boolalpha << "t2.joinable(): " << t2.joinable() << std::endl;

    system("pause");
}

这里写图片描述


scoped_thread

如果你嫌手动处理线程的生命周期太麻烦,可以在自己的包装类中封装std::thread。
这个类应该在析构函数中自动调用join。当然,你可以反过来调用detach。但是你知道,分离存在一些问题。
Anthony Williams创造了这样有价值的类,他称之为scoped_thread。
在构造函数中,它检查线程是否可连接,并最终在析构函数中join。
由于复制构造函数和复制赋值运算符被声明为delete,因此无法复制或分配scoped_thread的对象:

// scoped_thread.cpp

#include <iostream>
#include <thread>
#include <utility>

class scoped_thread 
{
    std::thread t;
public:
    explicit scoped_thread(std::thread t_) 
        : t(std::move(t_)) 
    {
        if (!t.joinable())
        {
            throw std::logic_error("No thread");
        }
    }
    ~scoped_thread() 
    {
        t.join();
    }
    scoped_thread(scoped_thread&) = delete;
    scoped_thread& operator=(scoped_thread const &) = delete;
};

int main() 
{
    scoped_thread t(std::thread([] 
    {
        std::cout << std::this_thread::get_id() << std::endl; 
    }));

    return 0;
}

原文:

http://www.modernescpp.com/index.php/threads-lifetime

猜你喜欢

转载自blog.csdn.net/y396397735/article/details/80979430