C++多线程编程之创建线程的几种方法

简介: C++多线程编程之创建线程的几种方法

1.线程基础知识


可执行程序运行起来,就会生成一个进程,该进程所属的主线程开始自动运行。请看下面的示例程序:


#include <iostream>
using namespace std;
int main(){
    cout << "I love China!" << endl;  // 实际上这个是主线程在执行,主线程从main()函数返回,则整个进程执行完毕。
    return 0;
}


主线程从main()函数开始执行,当我们自己创建线程时,也需要从一个函数开始运行(初始函数),一旦这个函数运行完毕,就代表我们这个线程运行结束。整个进程是否执行完毕的标志主线程是否执行完,如果主线程执行完毕,就代表整个进程执行完毕。此时,如果其他子线程还没有执行完,那么这些子线程也会被操作系统强行终止。因此,一般情况下,如果想保持子线程(自己用代码创建的线程)的运行状态,则必须要让主线程一直保持运行,不要让主线程运行结束。


2.创建线程的几种常用方法



2.1 使用初始函数创建线程


  • 主要流程如下:
  • 包含一个头文件thread
  • 编写初始函数
  • 在main()函数中开始写代码


#include <iostream>
#include <thread>
using namespace std;
// 自己创建的线程也要从一个函数(初始函数)开始运行
void myprint()
{
    cout << "我的线程开始执行了\n";
    /*
        中间包含其他的业务逻辑代码
    */
    cout << "我的线程执行完毕了\n";
}
int main(){
    thread mytobj(myprint);
    mytobj.join();
    cout << "I love China!" << endl;
    return 0;
}


   观察上面的代码发现:有两个线程在运行,相当于整个程序的执行有两条线在同时走。所以可以同时干两件事。即使一条线被堵住了,另外一条线仍然是可以通行的,这就是多线程。

73.png

(1).thread:是标准库中的类


// myprint是可调用对象
thread mytobj(myprint);
}


  • 上面的代码段做了两件事:
  • a.创建了线程,线程执行起点(入口)myprint();
  • b.myprint线程开始执行


(2).join()


加入/汇合,简单来说就是阻塞。阻塞主线程,让主线程等待子线程执行完毕,然后子线程和主线程汇合。然后主线程再继续往下走!


thread mytobj(myprint); 
mytobj.join();


  • 上面代码段的分析:
  • a.主线程阻塞到这里等待myprint子线程执行完,当子线程执行完毕,这个join()就执行完毕,主线程再继续往下执行。换言之,即阻塞主线程并等待myprint()子线程执行完
  • b.如果主线程执行完毕,但子线程没执行完毕,这样的程序是不合格的!程序也是不稳定的。一个良好的程序,应该是主线程等待子线程执行完毕后,主线程才能最终退出


(3).detach()  


detach()函数即主线程不再和子线程汇合。主线程执行自己的,子线程也执行自己的,主线程也不必等待子线程运行结束。主线程可以先执行结束,这并不影响子线程的执行    由于我们创建了很多子线程,让主线程逐个等待子线程结束,这种方法不太好,所以引入detach()方法。    一旦detach()之后,与主线程关联的thread对象就会失去与主线程的关联性。此时,子线程就会驻留在后台运行了(主线程与该子线程失去联系),子线程就相当于被C++运行时库接管。当子线程执行完毕后,由运行时库负责清理该线程相关的资源(守护线程)。    detach()使线程myprint失去我们对其进行的控制。一旦调用了detach(),就不能再用join(),否则系统会报告异常


(4).joinable()


判断是否可以成功使用join()或者detach()。若返回true,则可以使用join或者detach。否则,则不能使用。


thread mytobj(myprint); 
    if (mytobj.joinable())
    {
        cout << "1:joinable() == true" << endl;
    }
    else
    {
        cout << "1:joinable() == false" << endl;
    }
    mytobj.detach(); // 阻塞主线程并等待myprint()子线程执行完
    if (mytobj.joinable())
    {
        cout << "2:joinable() == true" << endl;
    }
    else
    {
        cout << "2:joinable() == false" << endl;
    }


2.2 使用类对象创建线程


#include <iostream>
#include <thread>
using namespace std;
class TA
{
public:
    void operator()() // 类对象变成可调用对象,即后面的括号中不能带参数
    {
        cout << "我的线程operator()开始执行了\n";
        /*
            中间包含其他的业务逻辑代码
        */
        cout << "我的线程operator()结束执行了\n";
    }
};
int main(){
    TA ta;  // ta是一个类的对象
    thread mytobj3(ta); // 同时,ta也是一个可调用对象
    mytobj3.join(); // 等待子线程执行结束
    cout << "I Love China" << endl;
    return 0;
}


使用类对象创建线程的代码如上所示,但需要注意一个问题。请看如下代码


#include <iostream>
#include <thread>
using namespace std;
class TA
{
public:
    int &m_i;
    TA(int& i) : m_i(i){}
    void operator()() // 类对象变成可调用对象,即后面的括号中不能带参数
    {
        cout << "m_i1的值为: " << m_i << endl; // 产生不可意料的结果!
        cout << "m_i2的值为: " << m_i << endl;
        cout << "m_i3的值为: " << m_i << endl;
        cout << "m_i4的值为: " << m_i << endl;
        cout << "m_i5的值为: " << m_i << endl;
        cout << "m_i6的值为: " << m_i << endl;
    }
};
int main(){
    int myi = 6;
    TA ta(myi);  // ta是一个类的对象
    thread mytobj3(ta); // 同时,ta也是一个可调用对象
    // mytobj3.join(); // 等待子线程执行结束
    mytobj3.detach();
    cout << "I Love China" << endl;
    return 0;
}


分析上述代码的输出结果可知:由于使用了mytobj3.detach(),因此主线程与子线程之间是无关的。主线程执行主线程的,子线程执行子线程的。现在假设出现一种情况,主线程先执行完了,子线程后执行完的。由于子线程的函数中使用的是引用成员变量m_i,m_i是与形参引用变量i绑定在一起的。而形参变量i是主线程中实参myi的引用,myi是主线程中的局部变量,当主线程执行结束后,变量myi的内存空间就会被系统所回收。但是,子线程仍然没有执行完,还在打印已经被销毁的变量m_i的内容,那么就会产生不可意料的结果了。


对上述代码段还有一个疑问:一旦调用了detach(),当主线程执行结束了,主线程中用的这个ta对象还存在吗?答:这个ta对象已经不存在了,因为这个对象ta实际上是被复制到子线程中去的!所以执行完主线程后,ta对象会被销毁,但是所复制的ta对象依旧存在。所以,只要你这个TA类对象里没有引用、没有引用,那么就不会产生问题。验证代码如下所示:


#include <iostream>
#include <thread>
using namespace std;
class TA
{
public:
    int &m_i;
    TA(int& i) : m_i(i){
        cout << "TA构造函数被执行" << endl;
    }
    TA(const TA& ta) : m_i(ta.m_i)
    {
        cout << "TA()拷贝构造函数被执行" << endl;
    }
    ~TA()
    {
        cout << "TA()析构函数被执行" << endl;
    }
    void operator()() // 类对象变成可调用对象,即后面的括号中不能带参数
    {
        cout << "m_i1的值为: " << m_i << endl;
        cout << "m_i2的值为: " << m_i << endl;
        cout << "m_i3的值为: " << m_i << endl;
        cout << "m_i4的值为: " << m_i << endl;
        cout << "m_i5的值为: " << m_i << endl;
        cout << "m_i6的值为: " << m_i << endl;
    }
};
int main(){
    int myi = 6;
    TA ta(myi);  // ta是一个类的对象
    thread mytobj3(ta); // 同时,ta也是一个可调用对象
    // mytobj3.join(); // 等待子线程执行结束
    mytobj3.detach();
    cout << "I Love China" << endl;
    return 0;
}

74.png


2.3 使用lambda表达式创建线程


int main(){
    auto mylamthread = []
    {
        cout << "我的线程3开始执行了" << endl;
        // ...
        cout << "我的线程3执行结束了" << endl;
    };
    thread mytobj4(mylamthread);
    mytobj4.join();
    return 0;
}
相关文章
|
14天前
|
Java 开发者
Java面试题:请解释内存泄漏的原因,并说明如何使用Thread类和ExecutorService实现多线程编程,请解释CountDownLatch和CyclicBarrier在并发编程中的用途和区别
Java面试题:请解释内存泄漏的原因,并说明如何使用Thread类和ExecutorService实现多线程编程,请解释CountDownLatch和CyclicBarrier在并发编程中的用途和区别
17 0
|
15天前
|
安全 Python
告别低效编程!Python线程与进程并发技术详解,让你的代码飞起来!
【7月更文挑战第9天】Python并发编程提升效率:**理解并发与并行,线程借助`threading`模块处理IO密集型任务,受限于GIL;进程用`multiprocessing`实现并行,绕过GIL限制。示例展示线程和进程创建及同步。选择合适模型,注意线程安全,利用多核,优化性能,实现高效并发编程。
28 3
|
15天前
|
缓存 Java 调度
Java并发编程:深入解析线程池与Future任务
【7月更文挑战第9天】线程池和Future任务是Java并发编程中非常重要的概念。线程池通过重用线程减少了线程创建和销毁的开销,提高了资源利用率。而Future接口则提供了检查异步任务状态和获取任务结果的能力,使得异步编程更加灵活和强大。掌握这些概念,将有助于我们编写出更高效、更可靠的并发程序。
|
2天前
|
监控 Java
Java并发编程:深入理解线程池
在Java并发编程领域,线程池是提升应用性能和资源管理效率的关键工具。本文将深入探讨线程池的工作原理、核心参数配置以及使用场景,通过具体案例展示如何有效利用线程池优化多线程应用的性能。
|
10天前
|
安全 Java 开发者
Java并发编程中的线程安全性与性能优化
在Java编程中,处理并发问题是至关重要的。本文探讨了Java中线程安全性的概念及其在性能优化中的重要性。通过深入分析多线程环境下的共享资源访问问题,结合常见的并发控制手段和性能优化技巧,帮助开发者更好地理解和应对Java程序中的并发挑战。 【7月更文挑战第14天】
|
10天前
|
监控 Java API
Java并发编程之线程池深度解析
【7月更文挑战第14天】在Java并发编程领域,线程池是提升性能、管理资源的关键工具。本文将深入探讨线程池的核心概念、内部工作原理以及如何有效使用线程池来处理并发任务,旨在为读者提供一套完整的线程池使用和优化策略。
|
12天前
|
Java
不自定义异步方法的线程池默认使用SimpleAsyncTaskExecutor
不自定义异步方法的线程池默认使用SimpleAsyncTaskExecutor
|
13天前
|
存储 安全 算法
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第72天】 在现代软件开发中,尤其是Java应用开发领域,并发编程是一个无法回避的重要话题。随着多核处理器的普及,合理利用并发机制对于提高软件性能、响应速度和资源利用率具有重要意义。本文旨在探讨Java并发编程的核心概念、线程安全的策略以及性能优化技巧,帮助开发者构建高效且可靠的并发应用。通过实例分析和理论阐述,我们将揭示在高并发环境下如何平衡线程安全与系统性能之间的关系,并提出一系列最佳实践方法。
|
14天前
|
设计模式 安全 Java
Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
23 0
|
14天前
|
Java 开发者
Java面试题:Java内存管理精要与多线程协同策略,Java内存管理:堆内存、栈内存、方法区、垃圾收集机制等,多线程编程的掌握,包括线程创建、同步机制的原理
Java面试题:Java内存管理精要与多线程协同策略,Java内存管理:堆内存、栈内存、方法区、垃圾收集机制等,多线程编程的掌握,包括线程创建、同步机制的原理
17 0