c++11多线程第二篇:线程启动结束创建线程的多个方法:joindetach(代码片段)

森明帮大于黑虎帮 森明帮大于黑虎帮     2022-12-06     259

关键词:

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多线程创建多个线程数据共享问题

目录1.创建和等待多个线程2.数据共享问题分析2.1只读数据2.2有读有写:3.共享数据的保护案例代码1.创建和等待多个线程a)多个线程执行顺序是乱的,跟操作系统内部对线程的运行调度机制有关;b)主线程等待所有子线... 查看详情

c++11多线程入门<学习记录;(代码片段)

最近学习了c++多线程相关知识,也算是对这方面内容的入门视频链接c++11并发与多线程视频课程看了大概两周,简单进行总结参考文章C++11并发与多线程PS:c++11提供了标准的可跨平台的线程库,本次多线程开发以此库为核心一.并... 查看详情

linux下多进程或者多线程编程的问题。新手,望指教!

...到中间,用fork创建多个进程,或者用其他方法创建多个线程,而线程是无线循环的,那么main函数还好在创建这些线程或者进程之后继续执行吗?还是停止在那个创建语句上?然后,main函数如果继续执行的话,那么他执行完毕,... 查看详情

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

】如何实现多线程中一个子线程先结束,主线程继续执行【英文标题】:Howtoachievethatasub-threadinmultithreadingendsfirst,andtheprimarythreadcontinuestoexecute【发布时间】:2022-01-0501:11:35【问题描述】:我尝试实现一个功能:主线程创建多个子... 查看详情

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

】c++boost线程问题[关闭]【英文标题】:c++boostthreadingissue[closed]【发布时间】:2014-11-1421:21:34【问题描述】:我对C++多线程很陌生。我有以下代码流:主线程:创建队列创建第二个线程将项目添加到队列中结束第二个线程:如果... 查看详情

c++11多线程

小彭老师的课程c++多线程没有多线程的时候,在执行download的时候会卡住在引入多线程之后,就可以在另一个线程中执行download,而在主线程中执行interactjoin函数的作用没有join的话,子线程可能会由于主线程退出而被迫退出所以... 查看详情

4.c++语言级别的多线程编程

通过thread类编写C++多线程程序线程内容:1、如何创建启动一个线程?​ std::thread定义一个线程对象,传入线程所需的线程函数和参数,线程自动开启2、子线程如何结束?​ 子线程函数运行完成,线程就结束了3、主线程如何处... 查看详情

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

1.Python多线程、多进程目的提高并发 1.一个应用程序,可以有多进程和多线程 2.默认:单进程,单线程 3.单进程,多线程   IO操作,不占用CPU   python的多线程:IO操作,多线程提供并发 计算性操作 多进程提高并发 4.GIL... 查看详情

秒杀多线程第二篇多线程第一次亲密接触createthread与_beginthreadex本质区别(代码片段)

   本文将带领你与多线程作第一次亲密接触,并深入分析CreateThread与_beginthreadex的本质区别,相信阅读本文后你能轻松的使用多线程并能流畅准确的回答CreateThread与_beginthreadex到底有什么区别,在实际的编程中到底... 查看详情

多线程的建立

importthreading#第一步,定义需要多线程运行的函数deftest(i):print(1)list1=[]#创建存放多线程的列表#第二步,生成。分别建立多个线程,a,b同时执行一个相同的任务foriinrange(10):th=threading.Thread(target=test,args=[i])#这里的th就是生成的多个线... 查看详情

c++11thread多线程创建和传参

...菜,你也可以派两个人一个买盐一个买菜,这里的人好比线程。进程就是线程的容器。任何程序执行都会有一个主线程,在c++中就是主函数所在的线程,那么其他线程也需要一个函数去执行,不然其他线程鬼知道自己要干什么。... 查看详情

秒杀多线程第二篇多线程第一次亲密接触createthread与_beginthreadex本质区别

   本文将带领你与多线程作第一次亲密接触,并深入分析CreateThread与_beginthreadex的本质区别,相信阅读本文后你能轻松的使用多线程并能流畅准确的回答CreateThread与_beginthreadex到底有什么区别,在实际的编程中到底应... 查看详情

《java多线程——线程简介与其创建》

Java给多线程编程提供了内置的支持。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。这里定... 查看详情

40.c++11多线程(代码片段)

语言级别的多线程=》代码跨平台Windows、linux、macosthread/mutex/condition_variableatomic原子类型,基于CAS操作的原子类型(线程安全)sleep_for本质上都是在调用系统的API一、如何创建启动线程?std::thread定义一个线程对... 查看详情

servlet第二篇servlet实现线程安全及其他细节补充(代码片段)

...ervice方法再根据请求方式分别调用doXXX方法。二、Servlet与线程安全?因为一个类型的Servlet只有一个实例对象,那么就有可能会出现一个Servlet同时处理多个请求,那么Servlet是否为线程安全的呢?答案:“不是线程安全的”。这说... 查看详情

012期javase面试题:多线程(代码片段)

...今天这篇是JavaSE系列的第十二篇,主要总结了Java中的多线程问题,多线程分为三篇来讲,这篇是第二篇,在后续,会沿着第一篇开篇的知识线路一直总结下去,做到日更!如果我能做到百日百更,希望你也可以跟着百日百刷,... 查看详情

毕设扫描器参数fuzz第二篇:动态爬虫的创建启动和协程池(代码片段)

...9;WireShark本地抓包(找到代码节点)CrawlerGo设置多线程爬虫爬虫调用代码创建爬虫任务启动爬虫任务把任务添加到线程池重要函数表格接着梳理协程任务的执行启动goroutine执行操作goroutine的操作:task.Task配置引擎的库&... 查看详情

40.c++11多线程(代码片段)

语言级别的多线程=》代码跨平台Windows、linux、macosthread/mutex/condition_variableatomic原子类型,基于CAS操作的原子类型(线程安全)sleep_for本质上都是在调用系统的API一、如何创建启动线程?std::thread定义一个线程对... 查看详情