C++11多线程第二篇:线程启动结束创建线程的多个方法:joindetach

Posted 森明帮大于黑虎帮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++11多线程第二篇:线程启动结束创建线程的多个方法:joindetach相关的知识,希望对你有一定的参考价值。

2、线程启动、结束、创建线程的多个方法:join、detach

2.1 范例演示线程运行的开始和结束

  • 程序运行起来,生成一个进程,该进程所属的主线程开始自动运行。
std::cout<<"C++"<<std::endl;
  • 实际上是这个主线程在执行,主线程从main()函数返回,则整个进程执行完毕。
  • 主线程从main()开始执行,那么我们自己创建的线程,也需要从一个函数开始运行(初始函数),一旦这个函数运行完毕,线程也结束运行。
  • 整个进程是否执行完毕的标志是 主线程是否执行完,如果主线程执行完毕了,就代表整个进程执行完毕了。
  • 此时,一般情况下,如果其他子线程还没有执行完毕,那么这些子线程也会被操作系统强行终止。
  • 所以,一般情况下,得到一个结论:如果大家想保持子线程(自己用代码创建的线程)运行状态的话,主线程不能停止运行。

2.1.1 thread

std::thread obj(MyPrint); //创建了线程,线程入口函数MyPrint(),然后MyPrint()函数开始执行。
  • 创建线程主要包含以下几步:

    • 包含一个头文件thread。

    • 线程入口初始函数要写。

    • 入口初始函数是一个可以调用的对象。一组可以执行的语句称为可调用对象,C++中可调用对象可以是函数、函数指针、lambda表达式、bind绑定对象、还可以是重载了()的类对象( operator() )。

    • 必须要明白:有两个线程在跑,相当于整个程序中有两条线在同时走,即使一条被阻塞,另一条也能运行。

thread是一个标准库的类。

2.1.2 join()加入/汇合

obj.join();  //阻止主线程执行,而是等待子线程执行完毕join()才算执行完毕,然后才执行主线程。
  • 就是阻塞,阻塞主线程,让主线程等待子线程执行完毕,然后子线程和主线程汇合,在执行主线程代码。**

  • 一个良好的线程程序,应该是主线程等待子线程执行完毕之后,主线程才退出来。

2.1.3 detach()分离

obj.detach();  //主线程与子线程分离开来,就是主线程自己执行自己的主线程代码,子线程执行自己的子线程代码,主线程不必等待子线程运行结束。
  • detach():主线程与子线程分离开来,就是主线程自己执行自己的主线程代码,子线程执行自己的子线程代码,主线程不必等待子线程运行结束。

  • 为什么引入detach():假如创建了许多子线程,让主线程逐个等待子线程结束,一般这种情况是不友好的,所以引入了detach()。一旦detach() 之后,与这个主线程关联的thread对象就会失去与主线程之间的关联,此时的子线程就会驻留在后台运行(主线程与子线程失去联系)。

  • 这个子线程就相当于被C++运行时库接管,当这个子线程执行完成后,就会由C++运行时库负责清理该线程相关的资源。(守护进程)

  • detach()使线程入口函数(子线程)失去对于自己的控制。

2.1.4 joinable()

  • joinable()判断是否可以成功使用join()或者detach()的,返回true或者false。true表示可以使用join()或者detach(),false表示不可以使用join或者detach()。
  • 一旦调用了join()或者detach(),后续就不能在调用了。

2.1.5 代码如下

#include<iostream>
#include<thread>

void MyPrint()
    std::cout<<"子线程开始--->"<<std::endl;
    
    std::cout<<"子线程结束1--->"<<std::endl;
    std::cout<<"子线程结束2--->"<<std::endl;
    std::cout<<"子线程结束3--->"<<std::endl;
    std::cout<<"子线程结束4--->"<<std::endl;
    std::cout<<"子线程结束5--->"<<std::endl;
    std::cout<<"子线程结束6--->"<<std::endl;
    std::cout<<"子线程结束7--->"<<std::endl;
    std::cout<<"子线程结束8--->"<<std::endl;
    std::cout<<"子线程结束9--->"<<std::endl;
    std::cout<<"子线程结束10--->"<<std::endl;

int main()
    std::thread obj(MyPrint);
    //obj.join();
    // if(obj.joinable())
    //     std::cout<<"1 true:joinable()"<<std::endl;
    // else
    //     std::cout<<"1 false:joinable()"<<std::endl;
    // 
    // obj.detach();
    // if (obj.joinable())
    //     std::cout << "2 true:joinable()" << std::endl;
    // 
    // else
    //     std::cout << "2 false:joinable()" << std::endl;
    // 
    if(obj.joinable())
        obj.detach();
    
    std::cout<<"主线程结束1"<<std::endl;
    std::cout<<"主线程结束2"<<std::endl;
    std::cout<<"主线程结束3"<<std::endl;
    std::cout<<"主线程结束4"<<std::endl;
    std::cout<<"主线程结束5"<<std::endl;
    std::cout<<"主线程结束6"<<std::endl;
    return 0;

输出结果:

主线程结束1
主线程结束2
主线程结束3
主线程结束4
子线程开始--->
子线程结束1--->
子线程结束2--->
子线程结束3--->
子线程结束4--->
子线程结束5--->
子线程结束6--->
子线程结束7--->
子线程结束8--->
子线程结束9--->
子线程结束10--->
主线程结束5
主线程结束6
PS C:\\Users\\Administrator\\Desktop\\thread\\build\\thread\\Debug> 

2.2 其他创建线程的手法

入口初始函数是一个可以调用的对象。一组可以执行的语句称为可调用对象,C++中可调用对象可以是函数、函数指针、lambda表达式、bind绑定对象、还可以是重载了opetator()成员函数的类对象( operator() )。

2.2.1 用类成员函数operator()、以及一个问题范例创建线程

  • 创建一个类,并写成员函数重载(),operator(),实例化化一个该类的对象,把该对象作为线程入口地址。
#include<iostream>
#include<thread>

class A
public:
    void operator()()
        std::cout<<"子线程开始1"<<std::endl;
        std::cout<<"子线程结束1"<<std::endl;
    
;

int main()
    A a;
    std::thread obj(a);
    obj.join();
    std::cout<<"主线程结束"<<std::endl;
    return 0;

输出结果:

PS D:\\C++11多线程代码编写\\build\\thread线程创建方法2\\Debug> .\\main.exe     
子线程开始1
子线程结束1
主线程结束
PS D:\\C++11多线程代码编写\\build\\thread线程创建方法2\\Debug>
  • 另一种实例:当类中有成员变量时要小心一下情况:
#include<iostream>
#include<thread>

template<class T>
class A
public:
    A(T& a)
    :a_(a)
    
    ~A()
    void operator()()
        std::cout<<"子线程开始1"<<std::endl;
        std::cout<<"子线程结束1"<<std::endl;

        std::cout<<"a_1的值:"<<a_<<std::endl;
        std::cout<<"a_2的值:"<<a_<<std::endl;
        std::cout<<"a_3的值:"<<a_<<std::endl;
        std::cout<<"a_4的值:"<<a_<<std::endl;
        std::cout<<"a_5的值:"<<a_<<std::endl;
    
private:
    T a_;
;

int main()
    //A a;
    int b=10;
    A<int> a(b);
    std::thread obj(a);
    //obj.join();
    obj.detach();
    std::cout<<"主线程结束1"<<std::endl;
    std::cout<<"主线程结束2"<<std::endl;
    std::cout<<"主线程结束3"<<std::endl;
    std::cout<<"主线程结束4"<<std::endl;
    std::cout<<"主线程结束5"<<std::endl;
    std::cout<<"主线程结束6"<<std::endl;
    std::cout<<"主线程结束7"<<std::endl;
    std::cout<<"主线程结束8"<<std::endl;
    std::cout<<"主线程结束9"<<std::endl;
    std::cout<<"主线程结束10"<<std::endl;
    return 0;

这时会发现输出结果会有不一样的结果:

  • 输出结果1:
主线程结束2
主线程结束3
主线程结束4
主线程结束5
主线程结束6
主线程结束7
主线程结束8
主线程结束9
主线程结束10子线程开始1
子线程结束1
a_1的值:
主线程结束1
主线程结束2
主线程结束3
主线程结束4
主线程结束5
主线程结束6
主线程结束7
主线程结束8
主线程结束9
主线程结束10
子线程开始1
  • 输出结果2:
主线程结束1
主线程结束2
主线程结束3
主线程结束4
主线程结束5
主线程结束6子线程开始1
子线程结束1
a_1的值:
主线程结束7
主线程结束810
a_2的值:10
a_3的值:10
a_4的值:10
a_5的值:10

主线程结束9
主线程结束10
PS D:\\C++11多线程代码编写\\build\\thread线程创建方法2\\Debug>
  • 原因如下:
    • **main函数中变量b是一个局部变量,存储在栈区。**当使用detach()函数而不是join()函数的时候就会出现问题,如果子线程先于主线程先结束的话没有问题。但是当主线程先结束,子线程后结束的话:那么主线程中栈区变量自动销毁,变量b传递不过去,而子线程又是引用传递过去,两个值一样那么子线程就接受不到传过来的值,那么子线程就不能输出成员变量的值。
      • 解决方法:
        • 使用join()而不是detach()函数。
        • 类中不是引用来接受传递过去的值,而是普通接受,那么就是拷贝一份值到类中就可以。
#include<iostream>
#include<thread>

template<class T>
class A
public:
    A(T a)
    :a_(a)
    
    ~A()
    void operator()()
        std::cout<<"子线程开始1"<<std::endl;
        std::cout<<"子线程结束1"<<std::endl;

        std::cout<<"a_1的值:"<<a_<<std::endl;
        std::cout<<"a_2的值:"<<a_<<std::endl;
        std::cout<<"a_3的值:"<<a_<<std::endl;
        std::cout<<"a_4的值:"<<a_<<std::endl;
        std::cout<<"a_5的值:"<<a_<<std::endl;
    
private:
    T a_;
;

int main()
    //A a;
    int b=10;
    A<int> a(b);
    std::thread obj(a);
    //obj.join();
    obj.detach();
    std::cout<<"主线程结束1"<<std::endl;
    std::cout<<"主线程结束2"<<std::endl;
    std::cout<<"主线程结束3"<<std::endl;
    std::cout<<"主线程结束4"<<std::endl;
    std::cout<<"主线程结束5"<<std::endl;
    std::cout<<"主线程结束6"<<std::endl;
    std::cout<<"主线程结束7"<<std::endl;
    std::cout<<"主线程结束8"<<std::endl;
    std::cout<<"主线程结束9"<<std::endl;
    std::cout<<"主线程结束10"<<std::endl;
    return 0;

输出结果:

PS D:\\C++11多线程代码编写\\build\\thread线程创建方法2\\Debug> .\\main.exe     
主线程结束1
主线程结束2
主线程结束3
子线程开始1主线程结束4
主线程结束5
主线程结束6
主线程结束7
子线程结束1
a_1的值:
主线程结束810
a_2的值:10
a_3的值:10
a_4的值:10
a_5的值:10

主线程结束9
主线程结束10
PS D:\\C++11多线程代码编写\\build\\thread线程创建方法2\\Debug>
  • 其实可能还有一个疑问,对象a也是一个局部对象,当主线程main函数先于子线程结束的时候,那么对象也被销毁了,那为啥上面代码后面也还能执行后续代码呢?

    • 原因:当主线程先于子线程先结束的时候,对象a确实被销毁了。但是使用std:: thread obj(a)是把a对象复制了一份到线程中去了,所以能执行后续代码。所以只要这个对象不是以引用传递或者以指针形式传递就没有问题。
    • 代码演示:
    #include<iostream>
    #include<thread>
    
    template<class T>
    class A
    public:
        A(T a)
        :a_(a)
            std::cout<<"A的构造函数"<<std::endl;
        
        A(const A& A1)
        :a_(A1.a_)
            std::cout<<"A1的拷贝构造"<<std::endl;
        
        ~A()
            std::cout<<"A的析构函数"<<std::endl;
        
        void operator()()
            std::cout<<"子线程开始1"<<std::endl;
            std::cout<<"子线程结束1"<<std::endl;
    
            std::cout<<"a_1的值:"<<a_<<std::endl;
            std::cout<<"a_2的值:"<<a_<<std::endl;
            std::cout<<"a_3的值:"<<a_<<std::endl;
            std::cout<<"a_4的值:"<<a_<<std::endl;
            std::cout<<"a_5的值:"<<a_<<std::endl;
        
    private:
        T a_;
    ;
    
    int main()
        //A a;
        int b=10;
        A<int> a(b);
        std::thread obj(a);
        //obj.join();
        obj.detach();
        std::cout<<"主线程结束1"<<std::endl;
        std::cout<<"主线程结束2"<<std::endl;
        std::cout<<"主线程结束3"<<std::endl;
        std::cout<<"主线程结束4"<<std::endl;
        std::cout<<"主线程结束5"<<std::endl;
        std::cout<<"主线程结束6"<<std::endl;
        std::cout<<"主线程结束7"<<std::endl;
        std::cout<<"主线程结束8"<<std::endl;
        std::cout<<"主线程结束9"<<std::endl;
        std::cout<<"主线程结束10"<<std::endl;
        return 0;
    
    

    输出结果:

    A的构造函数
    A1的拷贝构造
    主线程结束1
    主线程结束2
    主线程结束3
    主线程结束4
    主线程结束5
    主线程结束6
    主线程结束7
    主线程结束8
    主线程结束9
    主线程结束10子线程开始1
    子线程结束1
    a_1的值:
    A的析构函数
    PS D:\\C++11多线程代码编写\\build\\thread线程创建方法2\\Debug>
    

2.2.2 用lambda表达式

#include<iostream>
#include<thread>

int main()
    auto lambadThread=[]()
        std::cout<<"子线程开始1"<<std::endl;
        std::cout<<"子线程结束1"<<std::endl;
        std::cout<<"子线程结束2"<<std::endl;
        std::cout<<"子线程结束3"<<std::endl;
        std::cout<<"子线程结束4"<<std::endl;
    ;
    std::thread obj(lambadThread);
    if(obj.joinable())
        obj.join();
    
    std::cout<<"主线程结束1"<<std::endl;
    return 0;

输出结果:

PS D:\\C++11多线程代码编写\\build\\thread线程创建方法3\\Debug> .\\main.exe
子线程开始1
子线程结束1
子线程结束2
子线程结束3
子线程结束4
主线程结束1
PS D:\\C++11多线程代码编写\\build\\thread线程创建方法3\\Debug>

以上是关于C++11多线程第二篇:线程启动结束创建线程的多个方法:joindetach的主要内容,如果未能解决你的问题,请参考以下文章

C++11多线程 创建多个线程数据共享问题

如何实现多线程中一个子线程先结束,主线程继续执行

c ++ boost线程问题[关闭]

iOS开发:深入理解GCD 第二篇(dispatch_groupdispatch_barrier基于线程安全的多读单写)

C++11多线程 多线程传参详解

Python进阶第二篇多线程消息队列queue