【Linux】线程控制的秘密:如何写出高效、稳定的多线程程序
【Linux】线程控制的秘密:如何写出高效、稳定的多线程程序
在上篇关于线程的文章中,我们已经比较详细的了解了关于线程得概念,以及简单得见识过了线程,本篇文章将对线程概念进行些补充,同时帮助大家实现对线程的控制,如:创建线程,等待线程,取消线程,终止线程。
1.1 线程的私有资源
我们知道线程是可以共享资源的,但是真的是所有资源都共享吗?实则不然,在操作系统中,线程是相对独立的。尽管共享的大部分数据,但是都有其独立的资源。 这些资源在同一进程的不同线程之间是隔离的。每个线程可以独立访问和修改自己的私有资源,而不会影响其他线程的私有资源。 在多线程编程中,线程私有资源的主要作用是解决资源竞争和数据共享的问题。通过将某些资源声明为线程私有,可以避免多个线程同时访问共享资源时引发的竞态条件(Race Condition)和数据不一致问题。 以下是线程私有的资源:
- 线程
ID
:内核观点中的LWP
. - 一组寄存器:线程切换时,当前线程的上下文数据需要被保存。
- 线程独立栈:线程在执行函数时,需要创建临时变量。
- 错误码:
errno
:线程因为错误而终结,需要告知父进程。 - 信号屏蔽字:不同线程对于信号的屏蔽需求不同。
- 调度优先级:线程也是需要被调度的,需要根据优先级进行合理调度。 现在我会验证线程的独立栈,下面是相关的代码:
/**
* 验证线程的私有属性
*/
#include <iostream>
#include <pthread.h>
void* threadFunc(void* arg) {
int localVar = 0;
std::cout << "Thread ID: " << pthread_self()
<< ", Address of localVar: " << &localVar << std::endl;
return nullptr;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, nullptr, threadFunc, nullptr);
pthread_create(&t2, nullptr, threadFunc, nullptr);
pthread_join(t1, nullptr);
pthread_join(t2, nullptr);
return 0;
}
//打印结果
/*
Thread ID: 19879076959808, Address of localVar: 0x7f822b18e4
Thread ID: 1987908552512, Address of localVar: 0x7f8219e4
*/
可以看到他们的地址是不一样的。
1.2 线程的共享资源
共享资源就比较好理解了,因为线程看到的都是同一块地址空间。 ![[Pasted image 2025011715905.png]] 如图所示。那么线程具体共享哪些资源的呢?
- 共享区、全局数据区、字符常量区、代码区:常规资源共享区。
- 文件描述符表:进行
IO
操作时,无需再次打开文件。 - 每种信号的处理方法:多线程共同构成一个整体,信号的处理地址必须一样。
- 当前工作目录:即便是多线程,也是在同一个工作目录下的。
- 用户
ID
和组ID
:进程属于某一个组中的某个用户,多线程也是如此。
/**
* 验证全局变量的共享性
*/
#include <iostream>
#include <pthread.h>
int sharedVar = 0;
void* threadFunc(void* arg) {
for (int i = 0; i < 5; ++i) {
++sharedVar;
std::cout << "Thread ID: " << pthread_self()
<< ", sharedVar: " << sharedVar << std::endl;
}
return nullptr;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, nullptr, threadFunc, nullptr);
pthread_create(&t2, nullptr, threadFunc, nullptr);
pthread_join(t1, nullptr);
pthread_join(t2, nullptr);
return 0;
}
/*
打印结果
Thread ID: Thread ID: 140254181824, sharedVar: 1402550206528, sharedVar: 22
Thread ID: 1402550206528, sharedVar:
Thread ID: 1402550206528, sharedVar: 4
Thread ID: 1402550206528, sharedVar: 5
Thread ID: 1402550206528, sharedVar: 6
Thread ID: 140254181824, sharedVar: 7
Thread ID: 140254181824, sharedVar: 8
Thread ID: 140254181824, sharedVar: 9
Thread ID: 140254181824, sharedVar: 10
*/
2.1 创建线程
创建线程,不管是在上一篇文章还是在前面的代码中都有所提及。
创建线程的函数就是pthread_create()
pthread_create
函数是POSIX标准中用于创建新线程的函数,它运行在同一进程中并发执行多个任务。
#include <pthread.h>
int pthread_create(pthread_t *thread, ct pthread_attr_t *attr,void *(*start_routine)(void *), void *arg);
参数说明:
pthread_t* thread
:- 用于存储创建的线程ID(线程句柄)。
- 线程ID在后续操作(如
pthread_join
)中用于标识线程。
ct pthread_attr_t* attr
:- 线程的属性。可以设置为
ULL
使用默认属性。 - 自定义属性可以用于指定线程栈大小、调度策略等。
- 线程的属性。可以设置为
void* (*start_routine)(void*)
:- 线程执行的函数指针。
- 函数的返回值可以通过
pthread_join
获取。
void* arg
:- 传递给函数的参数,如果线程函数需要多个参数,可以将参数打包为一个结构体后传递。 返回值:
0
:表示线程创建成功。- 非0:表示线程创建失败,返回错误代码。
虽然已经看了很多遍了,但是我还是要用这个函数来给大家演示一个现象。
代码语言:javascript代码运行次数:0运行复制#include <iostream>
#include <unistd.h>
#include <threads.h>
using namespace std;
void* run(void* arg){
while(true){
cout<<"我是子线程:"<<(char*)arg<<endl;
sleep(1);
}
return nullptr;
}
ct int num = 5;
int main(){
pthread_t pth[num];
for(int i = 1;i<=num;++i){
char name[64];
snprintf(name,sizeof name,"thread%d",i);
pthread_create(&pth[i-1],nullptr,run,name);
}
while(true){
cout<<"我是主线程: main"<<endl;
sleep(2);
}
return 0;
}
/*
打印结果
我是子线程:thread5
我是子线程:thread5
我是子线程:我是主线程: main
thread5
我是子线程:thread5
我是子线程:thread5
我是子线程:我是子线程:thread5thread5
*/
在这段代码中我创建了5个线程,然后给他们分别命名位thread[1-5]
。但是当我们打印结果后出来的却只有thread5
.
这是为什么呢?
其实这也是线程共享资源造成的错误,name
是在主线程栈区开辟的空间,多个线程实际上指向的是同一块空间,最后一次覆盖后,所有线程就都打印thread5
了。
![[Pasted image 202501222057.png]]
为了避免这个问题,我们应该区堆上开辟空间。
#include <iostream>
#include <unistd.h>
#include <threads.h>
using namespace std;
void* run(void* arg){
while(true){
cout<<"我是子线程:"<<(char*)arg<<endl;
sleep(1);
}
delete[] (char*)arg;
return nullptr;
}
ct int num = 5;
int main(){
pthread_t pth[num];
for(int i = 1;i<=num;++i){
char* name = new char[64];
snprintf(name,sizeof name,"thread%d",i);
pthread_create(&pth[i-1],nullptr,run,name);
}
while(true){
cout<<"我是主线程: main"<<endl;
sleep(2);
}
return 0;
}
/*
打印结果:
我是主线程: main
我是子线程:thread2
我是子线程:thread1
我是子线程:thread4
我是子线程:thread
我是子线程:thread5
我是子线程:我是子线程:thread4thread2
我是子线程:thread1
我是子线程:thread
*/
这时肯定有人问了,堆区也是共享资源啊,为什么不会被覆盖?
这个就牵扯到计算机对不同空间的处理了。 在第一段代码中,
name
是局部变量,name内存地址是相同的因为栈帧被重复利用了。 而第二段代码中,name
是动态分配的内存,存储在堆上,每次给name
分配的地址是不同的。
2.2 等待线程
我们知道,进程是存在等待机制的,其实线程也是存在等待机制的。
线程等待的存在是为了在多线程程序中协调线程间的执行顺序,确保资源的正确访问和结果的有序生成。它通过协调线程间的执行顺序、确保资源的安全访问、回收线程资源以及实现线程间的通信,保证了多线程程序的稳定性和正确性。
Linux 中的 POSIX 线程库提供了 pthread_join
函数来实现这种等待机制。
2.2.1 pthread_join
函数
代码语言:javascript代码运行次数:0运行复制int pthread_join(pthread_t thread, void **retval);
参数说明
thread
:- 类型:
pthread_t
。 - 作用:指定要等待的目标线程 ID。
- 该值通常是由
pthread_create
创建线程时返回的。
- 类型:
retval
:- 类型:
void **
。 - 作用:接收目标线程的返回值,即
pthread_exit
的参数。 - 如果不需要获取线程返回值,可以传入
ULL
。 返回值
- 类型:
0
: 表示成功等待目标线程结束。- 错误码:
ESRCH
: 指定的线程不存在。EIVAL
: 线程不可连接,例如它已经分离。EDEADLK
: 调用线程与被等待线程之间存在死锁。
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
void* run(void* arg){
int cnt = ;
while(cnt--){
cout<<"我是子线程:"<<(char*)arg<<endl;
sleep(1);
}
delete[] (char*)arg;
return nullptr;
}
int main(){
pthread_t pth[5];
for(int i = 1;i<=5;++i){
char* name = new char[64];
snprintf(name,64,"pthread%d",i);
pthread_create(&pth[i-1],nullptr,run,name);
}
int cnt = 1;
for(int i = 1;i<=5;++i){
if(pthread_join(pth[i-1],nullptr)!=0){
perror("等待失败");
exit(1);
}else{
printf("成功等待%d个线程\n",cnt++);
}
sleep(1);
}
}
/*
打印结果:
我是子线程:pthread1
我是子线程:pthread2
我是子线程:pthread4
我是子线程:pthread
我是子线程:pthread5
我是子线程:pthread2
我是子线程:pthread
我是子线程:pthread1
我是子线程:pthread4
我是子线程:pthread5
我是子线程:pthread2
我是子线程:pthread
我是子线程:pthread1
我是子线程:pthread4
我是子线程:pthread5
成功等待1个线程
成功等待2个线程
成功等待个线程
成功等待4个线程
成功等待5个线程
*/
2. 终止线程
我们知道,终止一个进程的函数是exit
,那么终止一个线程的函数又是什么呢?
在多线程编程中,线程的终止是一个重要的操作。线程可以通过多种方式终止,例如正常执行完毕、显式调用终止函数、或者被其他线程强制终止。
线程终止主要有种方式,pthread_exit
、pthread_cancel
和正常返回。
2..1 pthread_exit
函数
代码语言:javascript代码运行次数:0运行复制void pthread_exit(void *retval);
参数说明:
retval
:
- 类型:
void *
。 - 作用:用于指定线程的退出状态(返回值)。
- 使用场景:
- 如果线程被
pthread_join
回收,则通过pthread_join
的第二个参数获取该值。 - 可以将简单的返回值(如整型或字符串指针)通过
retval
传递给回收线程。 - 注意:
retval
的内存管理由调用者负责,通常在退出前动态分配或者使用静态内存。
- 如果线程被
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
void* run(void* arg){
int cnt = ;
while(cnt--){
cout<<"我是子线程:"<<(char*)arg<<endl;
sleep(1);
}
pthread_exit(arg);
}
int main(){
pthread_t pth[5];
void* ret;
for(int i = 1;i<=5;++i){
char* name = new char[64];
snprintf(name,64,"pthread%d",i);
pthread_create(&pth[i-1],nullptr,run,name);
}
int cnt = 1;
for(int i = 1;i<=5;++i){
if(pthread_join(pth[i-1],&ret)!=0){
perror("等待失败");
exit(1);
}else{
cout<<"成功等待线程:"<<(char*)ret<<endl;
}
sleep(1);
}
}
/*
打印结果:
我是子线程:pthread
我是子线程:pthread2
我是子线程:pthread1
我是子线程:pthread4
我是子线程:pthread5
我是子线程:pthread
我是子线程:pthread2
我是子线程:pthread1
我是子线程:pthread4
我是子线程:pthread5
我是子线程:我是子线程:pthread1pthread
我是子线程:pthread2
我是子线程:pthread4
我是子线程:pthread5
成功等待线程:pthread1
成功等待线程:pthread2
成功等待线程:pthread
成功等待线程:pthread4
成功等待线程:pthread5
*/
如此一来,我就可以拿到线程的名字了。
注意:因为retval
的类型是void*
,所以甚至我可以返回一个结构体/对象。
2..2 pthread_cancel
函数
代码语言:javascript代码运行次数:0运行复制int pthread_cancel(pthread_t thread);
参数说明
thread
:- 类型:
pthread_t
。 - 作用:表示目标线程的线程标识符。
- 使用场景:
- 调用者通过该标识符指定需要被取消的线程。
- 通常此标识符由
pthread_create
返回。
- 类型:
返回值
- 类型:
int
。 - 含义:
0
: 成功向目标线程发送了取消请求。- 非零:函数调用失败,返回错误码。
- 常见错误:
ESRCH
: 目标线程不存在或无效。EIVAL
: 无效的参数,例如未启用线程取消功能。
- 常见错误:
#include <pthread.h>
#include <iostream>
#include <unistd.h>
void* threadFunc(void* arg) {
while (true) {
std::cout << "子线程正在运行...\n";
sleep(1);
}
return nullptr;
}
int main() {
pthread_t thread;
pthread_create(&thread, nullptr, threadFunc, nullptr);
sleep();
pthread_cancel(thread); // 发送取消请求
pthread_join(thread, nullptr);
std::cout << "子线程已被取消.\n";
return 0;
}
/*
打印结果:
子线程正在运行...
子线程正在运行...
子线程正在运行...
子线程已被取消.
*/
实战环节,我会用线程来计算[1,100*i](1<=i<=5)
的和。
#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>
using namespace std;
enum Status{//枚举状态
OK=0,
ERROR
};
/*
创建一个线程数据类,其中的属性有线程名字,线程id,线程状态,线程准备计算和的右边界。
*/
class ThreadData{
public:
ThreadData(ct string& name,int n,int id)
:_name(name),
_n(n),
_id(id),
_result(0),
_status(Status::OK)
{}
~ThreadData(){}
public:
string _name;
int _n;
int _id;
int _result;
Status _status;
};
void* run(void* arg){
ThreadData* td = static_cast<ThreadData*>(arg);
for(int i = 0;i<=td->_n;++i){
td->_result+=i;
}
cout<<"线程: "<<td->_name<<"执行完毕"<<endl;
pthread_exit(arg);
}
int main()
{
pthread_t pths[5];
//创建线程
for(int i = 1;i<=5;++i){
char* name = new char[64];
snprintf(name,64,"thread-%d",i);
ThreadData* threadData = new ThreadData(name,i*100,i);
pthread_create(&pths[i-1],nullptr,run,threadData);
sleep(1);
}
void* retval = nullptr;
//等待线程
for(int i = 1;i<=5;++i){
if(pthread_join(pths[i-1],&retval)!=0){
perror("线程等待失败!");
exit(1);
}
ThreadData* td = static_cast<ThreadData*>(retval);
if(td->_status == Status::OK){
printf("线程%s,计算[1~%d]的结果为:%d\n",td->__str(),td->_n,td->_result);
}
delete td;
}
cout<<"所有线程退出完毕"<<endl;
return 0;
}
/*
打印结果:
线程: thread-1执行完毕
线程: thread-2执行完毕
线程: thread-执行完毕
线程: thread-4执行完毕
线程: thread-5执行完毕
线程thread-1,计算[1~100]的结果为:5050
线程thread-2,计算[1~200]的结果为:20100
线程thread-,计算[1~00]的结果为:45150
线程thread-4,计算[1~400]的结果为:80200
线程thread-5,计算[1~500]的结果为:125250
所有线程退出完毕
*/
程序可以正常运行,各个线程也能正常计算出结果;这里只是简单累加求和,线程还可以用于其他场景。比如:网络传输、密集型计算、多路IO
等等,无非就是修改线程的业务逻辑。
线程控制是多线程编程中不可或缺的一部分,通过合理管理线程的创建、运行、同步和终止,可以提升程序的效率、稳定性和资源利用率。在实际开发中,理解线程的生命周期、掌握线程间通信与同步机制,以及灵活运用线程控制函数如 pthread_create、pthread_exit 和 pthread_join,能够帮助开发者更好地实现并发编程的目标。同时,线程控制的精髓不仅在于技术的掌握,更在于对资源的合理调配和对任务的高效分配。掌握这些技巧,无疑将为构建高效、可靠的多线程程序打下坚实的基础。
往期专栏:Linux专栏:Linux
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2025-01-22,如有侵权请联系 cloudcommunity@tencent 删除程序多线程函数线程linux#感谢您对电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格的认可,转载请说明来源于"电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格
推荐阅读
留言与评论(共有 17 条评论) |
本站网友 玉林房屋出租 | 3分钟前 发表 |
则通过 pthread_join 的第二个参数获取该值 | |
本站网友 海星可以吃吗 | 27分钟前 发表 |
Address of localVar | |
本站网友 药物流产 | 11分钟前 发表 |
多个线程实际上指向的是同一块空间 | |
本站网友 牡丹江酒店 | 19分钟前 发表 |
i); pthread_create(&pth[i-1] | |
本站网友 山西王家岭煤矿 | 20分钟前 发表 |
sharedVar | |
本站网友 成人如何提高记忆力 | 14分钟前 发表 |
pthread1 成功等待线程 | |
本站网友 减肥产品排行榜 | 8分钟前 发表 |
threadFunc | |
本站网友 三个人的错 | 25分钟前 发表 |
nullptr); pthread_create(&t2 | |
本站网友 洛宁房屋出租 | 7分钟前 发表 |
作用:指定要等待的目标线程 ID | |
本站网友 膨胀 | 9分钟前 发表 |
运行 | |
本站网友 ab报 | 8分钟前 发表 |
4.总结线程控制是多线程编程中不可或缺的一部分 | |
本站网友 商会商务运作 | 28分钟前 发表 |
threadFunc | |
本站网友 乳液是什么 | 11分钟前 发表 |
用于存储创建的线程ID(线程句柄) | |
本站网友 含着他的阳具 | 23分钟前 发表 |
thread-4执行完毕 线程 | |
本站网友 毛孔粗怎么办 | 19分钟前 发表 |
本站网友 赤小豆的功效 | 13分钟前 发表 |
cnt++); } sleep(1); } } /* 打印结果: 我是子线程:pthread1 我是子线程:pthread2 我是子线程:pthread4 我是子线程:pthread 我是子线程:pthread5 我是子线程:pthread2 我是子线程:pthread 我是子线程:pthread1 我是子线程:pthread4 我是子线程:pthread5 我是子线程:pthread2 我是子线程:pthread 我是子线程:pthread1 我是子线程:pthread4 我是子线程:pthread5 成功等待1个线程 成功等待2个线程 成功等待个线程 成功等待4个线程 成功等待5个线程 */2. 终止线程我们知道 |