news 2026/4/30 4:00:33

C++初级线程管理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++初级线程管理

前言:

实际程序运行时,每个程序都有一个程序入口,线程也不例外,使用线程时,需要给线程提供一个入口函数,线程执行完入口函数时,线程将退出。C++11中提供了std::thread库,本文将从线程的启动、线程等待、线程分离、线程传参、线程识别等几个方面介绍初级线程管理的知识。

1 线程启动

C++11中,线程的启动终究是对std::thread的对象进行构造。

线程构造的类别如下:

1.1 线程函数无参数无返回值

此类可以说是最简单的线程启动,函数不需要传参也不需要返回函数执行结果,执行完成后,线程自动退出。

形如:

1

2

voidFunDoingNothing();

std::thread(FunDoingNothing)

编写代码时,需要加上<thread>头文件以方便编译器能够正确处理thread对象。

1.2 线程函数有参数无返回值

C+=11中,thread的构造函数中使用了可变参数,这样,可以使得构造thread对象时可以自定义传入参数,

构造函数的定义如下:

1

template<classF,class... Args>explicitthread(F&& f, Args&&... args);

在实际使用时,线程函数有参数时可以定义形式如下:

1

2

3

4

voidprintMsg(inta,intb) {

cout <<"input params are:"<< a <<","<<b<< endl;

}

std::threadmy_thread(printMsg, 3, 4)

1.3 调用可调用的类型构造

使用时,可以将带有执行函数的变量传入thread的构造函数中从而替换默认的构造函数,

如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

usingnamespacestd;

classBackGroundTask{

public:

voidoperator()()const{

doSomeThing();

}

priavte:

doSomeThing();

};

intmain(){

BackGroundTask f;

std::threadmyThread(f);

}

上面的代码中,在启动线程时同构构造对象f,f对象的重载函数中调用了线程运行时要执行的方法。但有一点需要注意的是,在传入临时的构造对象时,不经过处理,可能会让编译器产生错误的理解。

如:

1

std::threadmyThread(BackGroundTask());

这里相当与声明了一个名为myTread的函数, 这个函数带有一个参数(函数指针指向没有参数并返回BackGroundTask对象的函数), 返回一个std::thread对象的函数, 而非启动了一个线程。

如果要解决这个问题,只需要如下处理即可:

1

2

std::threadmyThread((BackGroundTask()));

std::threadmyThread{BackGroundTask()};

当然,也可以使用lamda表达式实现上述功能,如下:

1

2

3

std::threadmyThread([]{

doSomeThing();

});

2 等待线程

C++11中,确保线程执行完后,主线程在退出,需要在代码中使用join()函数,这样就可以保证变量在线程结束时才会进行销毁。

2.1 join等待

在实际编程时,join函数只是简单的等待或者不等待。在有些场景下就会不使用,如果想要进行更加灵活的控制,需要使用C++11中提供的其他机制,这个也会在后面的推文中进行说明。
在编程时,如果对一个线程使用了join,那么在后续的操作中如果使用joinable()执行结果将返回false。既一旦使用了join。线程对象将不能重复使用。如下代码中,在线程中使用join等待。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

classBackGroundTask

{

public:

voidoperator()()

{

doSomeThing();

}

private:

voiddoSomeThing() {cout<<"线程退出"<<endl;};

};

intmain()

{

BackGroundTask f;

std::threadmyThread(f);

myThread.join();

cout<<"退出"<<endl;

}

上面的代码使用了线程等待,可以输出正确的结果,如下:

线程退出
退出

如果将myThread.join()语句注释,再次执行时,程序将执行出错,因为在子线程还没有结束时,主线程已经结束。

运行结果如下:

退出
terminate called without an active exception

上面的输出具备不确定性,代码运行时结果随机。

2.2 异常场景的join等待

异常场景中,如果没有充分考虑join的位置,就可能会产生因为异常导致主线程先于子线程退出的情况,解决这些问题可以通过下面两种方法进行处理:

2.2.1 通过异常捕获

通过分析代码中的异常场景,对异常使用try...catch进行捕获,然后在需要线程等待的地方调用join()函数,这种方法虽然可以轻易地捕获问题并对问题进行修复,但并非是通用法则,还需要根据实际情况进行分析。如检查并确认是否线程函数中是否使用了局部变量的引用等其它原因。

2.2.2 使用RAII方式进行线程等待

RAII可以理解为资源获取既初始化。因为全写为:Resource Acquisition Is Initialization
实际使用时,通过定义一个类,然后在析构函数中使用join函数进行线程等待。这样可以避免场景有遗漏的地方。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

classthread_guard

{

private:

std::thread& t;

public:

explicitthread_guard(std::thread& t_):t(t_){}

~thread_guard()

{

if(t.joinable())

{

t.join();

}

}

thread_guard(thread_guardconst&)=delete;

thread_guard& operator=(thread_guardconst&)=delete;

};

如上,通过在将线程对象传入到类thread_guard中,如果thread_guard类对象的局部变量被销毁,则在析构函数中会将线程托管到原始线程。
thread_guard中,使用delete标识,禁止生成该类的默认拷贝构造、以及赋值函数。
在实际编程时如果不想线程等待,可以使用detach方法,将线程和主线程进行分离。

3 线程分离

线程分离使用detach方法,使用后将不能在对已分离的线程进行管理,但是分离的线程可以真实的在后台进行运行。当线程退出时,C++会对线程资源进行清理和回收。
线程分离通常被用作守护线程或者后台工作线程。

使用方法如下:

1

2

3

4

5

6

7

intmain()

{

BackGroundTask f;

std::threadmyThread(f);

myThread.detach();

cout<<"退出"<<endl;

}

4 向线程传递参数

向线程传递参数非常简单,在上面的代码中也有提及,这里主要说下向线程中传递参数的陷阱。

看下面的代码:

1

2

3

4

5

6

7

8

voidf(inti,std::stringconst& s);

voidoops(intsome_param)

{

charbuffer[1024];

sprintf(buffer,"%i",some_param);

std::threadt(f,3,buffer);

t.detach();

}

上面的代码中buffer是一个局部指针变量,使用后,可能会导致线程出现未定义的行为,因为从char*到string的转换时使用的是隐式转换,但是thread在使用时会将变量拷贝到线程私有内存,但是并不知道需要将参数进行转换,因此复制到私有内存的变量就没有转换成期望的对象。
如果要解决这个问题,可以在使用时直接将参数类型转换成函数默认的类型,在上面的例子中可以

做如下操作:

1

std::threadt(f,3,std::string(buffer));

但是这样做依然存在问题,既线程在复制变量到私有内存时,只复制了变量值,这样在线程调用后,如果继续使用线程函数处理后的变量时可能变量并没有改造,依旧是线程调用之前的变量。
因此要想在函数传参过程中使得线程拷贝时依旧保持引用,可以在线程调用时使用引用方式,

如:

1

std::threadt(f,3,std::ref(std::string(buffer)));

5 线程识别

每个线程都有一个线程标识,在C++11中,线程标识通过std::thread::id进行标识,std::thread::id可以复用并进行比较,如果两个线程的id相等,那么它们就是同一个线程或者没有线程,如果不等就表示两个是不同的线程或者其中一个线程不存在。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/30 3:55:22

NVIDIA Blackwell与Unsloth优化LLM训练实战

1. 项目概述&#xff1a;基于NVIDIA Blackwell与Unsloth的高效LLM训练方案在当前的AI领域&#xff0c;大型语言模型(LLM)的微调与强化学习(RL)训练一直面临着高门槛的技术挑战。传统方法不仅需要昂贵的硬件配置&#xff0c;还涉及复杂的工程化流程&#xff0c;这使得许多中小团…

作者头像 李华
网站建设 2026/4/30 3:45:27

深度学习模型训练与优化的核心技术与实践

1. 深度学习模型训练的核心逻辑深度学习模型的训练过程本质上是一个高维空间中的优化问题。我们通过反向传播算法调整数百万甚至数十亿个参数&#xff0c;让模型在训练数据上逐步逼近最优解。这个过程中有几个关键要素需要特别关注&#xff1a;首先是损失函数的选择。对于分类任…

作者头像 李华
网站建设 2026/4/30 3:37:21

PADS VX2.4保姆级教程:从颜色配置到布线选项,新手避坑指南

PADS VX2.4保姆级教程&#xff1a;从颜色配置到布线选项&#xff0c;新手避坑指南 第一次打开PADS VX2.4时&#xff0c;满屏的选项和参数确实容易让人望而生畏。作为一款功能强大的PCB设计工具&#xff0c;合理的初始配置不仅能提升工作效率&#xff0c;更能避免后期设计中的各…

作者头像 李华
网站建设 2026/4/30 3:34:23

纳米无人机自主导航技术:SWaP约束下的创新突破

1. 纳米无人机自主导航的技术挑战与机遇在微型飞行器领域&#xff0c;纳米级无人机&#xff08;Nano-UAVs&#xff09;正掀起一场技术革命。这些重量不足50克、处理器功耗低于100毫瓦的微型飞行器&#xff0c;正在突破传统航空器的物理极限。我曾参与过多个纳米无人机研发项目&…

作者头像 李华