Linux中线程的概念及创建应用

本文详细阐述了线程与进程的概念、区别以及多线程与多进程的优缺点。介绍了线程的创建、终止和等待函数,并解释了线程所拥有的资源。

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

一.线程的概念
线程,是程序执行流的最小单元。线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机 ; 运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。
每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
之前没有学过线程的概念之前,所谓的pid指的是进程的id,一个进程有一个对应的PCB。但是引入了线程的概念之后,一个进程实际上对应的是一组PCB,因为线程的描述也是由PCB来描述的,这样的一个进程内的一组线程叫做线程组。
对于系统调用getpid(),它返回的其实并不是这个进程的pid,而是这个线程组的组id(tgid),即“Thread Group ID“”。该线程组的组id就是该线程组的主线程的id(主函数main)。想要拿到具体某个轻量级进程(线程)的id,可以用系统调用gettid()。

实际上,线程有两个ID,一个是内核给每个线程的LWP(ID);另一个则是POSIX中的线程库的线程ID,也就是pthread_self()返回的线程ID,这个系统调用实际上返回的是进程地址空间上的一个地址。

二.线程与进程的区别
在Linux下并没有专门为线程设计这么一个概念,也就是说没有真正意义上的线程,它在Linux下是由进程模拟的,可以认为所有的PCB都可以称为轻量级进程(不一定是进程,也可能是线程),简称LWP。进程是分配系统资源的一个实体,而线程是CPU调度的基本单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。相对来说,多进程相对稳定,多线程相对不稳定。
进程具有独立的地址空间,而线程并没有,同一进程内部的线程共享进程的地址空间。

三.多线程相对于多进程的优缺点

1.多线程的优点:

  • 无需跨进程边界;

  • 程序逻辑和控制方式简单;

  • 所有线程可以直接共享内存和变量等;

  • 线程方式消耗的总资源比进程方式好;

2.多线程的缺点:

  • 每个线程与主程序共用地址空间,受限于有限的地址空间;

  • 线程之间的同步和加锁控制比较麻烦;

  • 一个线程的崩溃可能影响到整个程序的稳定性;

  • 到达一定的线程数程度后,即使再增加CPU也无法提高性能;

  • 线程能够提高的总性能有限,而且线程多了之后,线程本身的调度也很麻烦,需要消耗较多的CPU;

3.多进程的优点:

  • 每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系;

  • 通过增加CPU,就可以容易扩充性能;

  • 可以尽量减少线程加锁/解锁的影响,极大提高性能,就算是线程运行的模块算法效率低也没关系;

  • 每个子进程都有2GB地址空间和相关资源,总体能够达到的性能上限非常大;

4.多进程的缺点:

  • 逻辑控制复杂,需要和主程序交互;

  • 需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算;

  • 多进程调度开销比较大;

四.线程所具有的资源

线程共享的进程资源及环境:
1. 文件描述符表
2. 虚拟地址空间(实际上是页表,并不包括调用栈)
3. 每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)
4. 当前工作目录
5. 用户id和组id

每个线程私有一份的资源:
1. 栈空间
2. 上下文信息,包括各种寄存器的值、程序计数器和栈指针
3. 调度优先级
4. errno变量
5. 信号屏蔽字
6. 线程id

五.线程的创建/等待/终止

1.进程的创建函数pthread_create
这里写图片描述
参数声明:

  • thread:返回线程ID

  • attr:设置线程的属性,attr为NULL表示使用默认属性

  • start_routine:是个函数地址,线程启动后要执行的函数

  • arg:传给线程启动函数的参数

此函数成功返回0,失败返回错误号,实际上pthread中的函数失败的返回值都是返回错误号

关于错误信息的检查
- 传统的一些函数是成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误
- pthreads函数错误时不会设置全局变量errno(而大部分其他POSIX函数会这样做),而是将错误代码通过返回值返回。
- pthreads同样也提供了线程内的errno变量,以支持其他使用errno的代码。对于pthreads函数的错误,建议通过返回值判定,因为读取返回值的代价比读取线程内部的errno变量要小的多

2.进程的终止函数pthread_exit

实际上,线程的终止有三种方式:
①return可以表示线程终止
②使用pthread_exit表示线程终止
③一个线程可以调用pthread_cancel终止同一进程的另一个进程
这里写图片描述
参数声明:

  • retval:retval不要指向一个局部变量

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

3.进程的取消函数pthread_cancel
这里写图片描述
thread即为另一线程的线程ID

4.进程的等待函数pthread_join

  • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内
  • 创建新的进程不会服用刚才退出线程的地址空间

    这里写图片描述
    参数声明:

  • thread:线程ID

  • value_ptr:它指向一个指针,该指针指向线程的返回值

    同样的,成功返回0,失败返回错误号。

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过
pthread_join得到的终止状态是不同的,总结如下:

  • 如果thread线程通过return返回,value_ ptr所指向的单元⾥里存放的是thread线程函数的返回值。
  • 如果thread线程被别的线程调⽤用pthread_ cancel异常终止掉,value_ptr所指向的单元里存放的是常数PTHREAD_ CANCELED(通过验证可以得到该常数是-1)。
  • 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传pthread_exit的参数。
  • 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

5.线程的分离函数pthread_detach

  • 默认情况下,新创建的线程时joinable的,线程退出后,需要调用pthread_jion函数来等待,否则无法释放线程资源,导致系统内存泄漏。
  • 当我们不关心线程的返回值时,jion显然是多余的,这个时候我们可以调用pthread_detach函数来分离线程,告诉系统线程退出时,自动释放线程资源。
int pthread_detach(pthread_t thread);

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离。

pthread_detach(pthread_self());

joinable和分离是冲突的,一个线程不可能既是joinable的又是分离的。

相关的测试代码如下:
这里写图片描述

在这里,thread_run函数是线程去执行的函数,而main函数是主线程所执行的函数,下面我们来看线程的三种终止方式的退出结果。

如上图所示,是第一种线程终止的方式,利用pthread_cancel函数取消线程,其测试结果如图:
这里写图片描述
可以看到其返回值ret是-1。

下面看第二种线程终止的方式,利用pthread_exit函数终止线程,其测试代码及测试结果如图:
这里写图片描述
这里写图片描述
可以看到程序最终接受到的返回码就是123,是pthread_exit中的参数值。

最后一种线程终止的方式,就是在函数中直接return返回,其测试代码及测试结果如图:
这里写图片描述
这里写图片描述
可以看到程序最终返回的就是1,就是thread_run函数中return给主函数的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值