[Linux]——Linux线程控制

本文介绍Linux线程控制相关内容。因Linux无真正线程,无系统调用管理线程,故使用POSIX线程库。详细讲解了线程创建、等待、退出、获取线程id、分离等函数的使用及相关细节,如线程等待可防内存泄漏,线程退出不能用exit等,掌握这些函数可实现线程控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux线程控制

上篇文章我们介绍了线程的基本概念,详细叙述了线程的一些细节性知识点,我们之前写过进程控制的博客,但是我们今天所写的关于线程控制的函数与进程控制有所不同。相信你肯定还记得我们上篇博客中说明了在Linux下实际上没有真正意义上的线程,他们都是利用进程模拟的,那么也就意味着系统并没有提供一组系统调用接口来进行线程的控制,所以我们先来谈谈使用什么方案进行线程控制。

POSIX线程库

因为Linux中并没有真正的线程,cpu眼中看到的只有进程实体,所以线程就成了一个虚无的东西,也就意味着Linux并没有提供一组系统调用管理线程。那既然操作系统没有为我们提供结构,我们就自己实现一组接口来管理线程。

POSIX表示可移植操作系统接口,这里我们介绍的这些函数也遵循POSIX标准,这些函数是一组用户级别的调用,与有关的函数构成了一个完整的系列,大多数名字都是以"pthread_“开头的,而要使用函数库,我们就要引入头文件<pthread.h>,并且这里最重要的一点是我们在链接这些线程函数库时编译时需要加上”-pthread"选项。
在这里插入图片描述

线程创建

在这里插入图片描述

  • 参数一:返回线程的id,一个输出型参数
  • 参数二:设置线程的属性,使用nullptr表示默认属性
  • 参数三:让子线程执行函数的地址,函数的名称
  • 参数四:子线程执行函数的参数

我们来使用此函数来创建一个新的线程:
在这里插入图片描述
在这里插入图片描述
那么你怎么证明我创建了一个新的线程,我们这里使用ps -ajL,这里大L选项可以帮助我们查看有关的线程信息:
在这里插入图片描述
我们清楚的看到,这里存在俩个pthread,但是我们发现他们俩的pid都是相同的,细心的同学已经发现了,他们不同的属性是LWP,他是light weighted process的缩写,也就是轻量级进程的意思,这也就更好的解释了Linux下没有真正的进程这一观点。
所以实际上cpu调度一个任务并不是查看他的pid,而是lwp,那么我们之前讲解进程时所说的使用pid调度说法是错的么?当然不是,因为单线程进程的lwp和pid可以划等号的,如上图第一个线程。我们修改代码,将pthread_create函数的第一个参数也打印出来:
在这里插入图片描述
数字很大,我们使用计算机把它转换为16进制,没错,他看起来像一个地址,其实他就是一个地址。
在这里插入图片描述
我们之前一直在提POSIX线程库是一个用户级别的函数库,所以使用此库时我们会将此库动态映射到主进程地址空间的mmap区域,所以此字段要对线程进行线程进行描述需要先描述在组织,所以有了图中的struct_pthread,线程局部存储和线程栈,而结构体开始的地址就是找到某一个描述线程结构的地址,所以函数参数的tid参数就是这些线程描述的首地址。
在这里插入图片描述

线程等待

线程等待的原因与进程等待的愿意相同,一方面主进程希望拿到子进程的退出状态,并且主线程等待子线程防止造成内存泄漏的危害,这点与进程等待非常相似,可以参考博主的之前的文章查看。
在这里插入图片描述

  • 参数一:要等待的线程id
  • 参数二:要等待线程的退出码

这里有一个线程退出的小细节,join函数的第二个参数目的取到线程退出的退出码,这里仅仅是退出码,不像我们进程等待时那样可以判断是否是信号所终止的,因为我们之前在线程的缺点中提到过,因为线程是进程的一个分支,只要子线程出现了任何异常都会导致整个进程退出,所以不可能拿到异常码,仅仅可以拿到退出码。

在这里插入图片描述
程序打印三条消息后退出,主线程等待完毕并且打印子线程的退出码:如果你不想要这个退出码可以直接传NULL
在这里插入图片描述

线程退出

讲解线程退出前,问读者们一个问题,类比进程,那么我们的线程可以调用exit来退出吗?你始终不要忘记线程是进程的内部执行流,如果你调用exit就会导致整个进程退出。而上面我们在进程等待小节时使用了return让线程退出好像没什么问题,所以得出结论我们的线程退出可以使用return。
在这里插入图片描述
在这里插入图片描述
这里我们使用pthread_exit函数来进行退出同样可以拿到我们想要的结果:
在这里插入图片描述
接下来我们要说一个奇怪的退出方式,线程是可以被取消的,所以我们可以让主线程取消子线程,这里取消线程好像只能被别人取消,因为实验了很多次自己取消自己并没有拿到正确的状态码:
在这里插入图片描述
而且这里有一个细节是,当你在主线程中取消子线程时,需要保证子线程在主线程被调度之后被取消,否则将拿不到正确的结果。如果子线程被正确的取消那么join函数中的指针将会指向-1.

获得线程id

很简单,此函数没有参数,所以直接调用就可以获得线程id。但是我们之前一直在说线程id,可是我们只是通过命令看到一个叫lwp的东西,那么线程id和进程id在底层到底是什么样子的呢?
在这里插入图片描述
没有线程之前,一个进程对应内核里的一个进程描述符,对应一个进程ID。但是引入线程概念之后,情况发生了变化,一个用户进程下管辖N个用户态线程,每个线程作为一个独立的调度实体在内核态都有自己的进程描述符,进程和内核的描述符一下子就变成了1:N关系,POSIX标准又要求进程内的所有线程调用getpid函数时返回相同的进程ID,如何解决上述问题呢?

Linux内核引入了线程组的概念,多线程的进程,又被称为线程组,线程组内的每一个线程在内核之中都存在一个进程描述符与之对应。进程描述符结构体中的pid,表面上看对应的是进程ID,其实不然,它对应的是线程ID;进程描述的tgid,含义是Thread Group ID,该值对应的是用户层面的进程ID

你没有看错,笔者也没有写错,实际上,你看到的进程pid对应了test_struct中的tgid,而我们看到的lwp对应的是test_struct中的pid。

struct task_struct {
...
pid_t pid;
pid_t tgid;
...
struct task_struct *group_leader;//主线程
...
struct list_head thread_group;//用来描述一个线程组的链表
...
};

当你在用户态下一次调用gitpid你就要知道,其实系统给你返回了test_struct中的tgid。Linux中也提供了gittid用来返回线程id,但是此系统调用并没有封装起来,不是很方便使用。

线程分离

在默认的情况下,新创建的线程时joinable的,线程退出后,需要对他进行pthread_join操作,否则无法释放资源,从而造成内存泄漏,如果不关心线程的返回值,join是一种负担,所以我们可以选择将子线程分离,自动释放线程资源,线程可以自己将自己分离,也可以让别人帮助其分离。
在这里插入图片描述
这里有一个非常重要的点是,一个线程在被分离后就不能够进行等待,一定要记住这一点,这一点真的非常重要。
在这里插入图片描述
此时线程是即分离又被等待的状态,我们只关心pthread_join函数的返回值发现此时的返回值为22,说明并不是正常情况:
在这里插入图片描述

总结

本节我们讲解了一些关于线程控制的函数,首先我们要知道Linux下使用的线程控制函数其实上是一组用户级别的函数,其次只要我们能熟练的掌握这些函数的使用,相信对线程的控制并不是一件难事。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值