Linux线程详解(概念、原理、实现方法、优缺点)

article/2025/10/31 4:57:38

文章目录

  • 一、Linux线程基本概念
  • 二、Linux内核线程实现原理
  • 三、创建线程
  • 四、线程的优缺点

一、Linux线程基本概念

linux中,线程又叫做轻量级进程(light-weight process LWP),也有PCB,创建线程使用的底层函数和进程底层一样,都是clone,但没有独立的地址空间;而进程有独立地址空间,拥有PCB。
Linux下:线程是最小的执行单位,调度的基本单位。进程是最小分配资源单位,可看成是只有一个线程的进程。
线程是一个进程内部的控制序列。控制序列可以理解为一个执行流。进程内部是指虚拟地址空间。

1、线程特点:
(1)线程是资源竞争的基本单位。
操作系统有很多资源。进程与进程之间要竞争操作系统资源,当一个进程申请得到一大堆资源。而这些资源又会分配给线程。一个进程内部有多个线程,去竞争进程所获得的资源。所以说线程是资源竞争的基本单位。
(2)线程是程序执行的最小单位
当用户让进程去执行某个任务时,进程又会将任务细化。进程内部有很多线程,让这些线程去执行
(3)线程共享进程数据,但也拥有自己独立的一部分数据: 线程ID ,一组寄存器,栈,errno值,信号。
其中最重要的数据是栈和寄存器。私有栈是为了保存临时变量,便于函数调用等操作。私有寄存器是为了方便线程切换,保存上下文。

2、进程到线程:
进程:承担分配系统资源的实体
线程:共享进程所获得资源
在这里插入图片描述

二、Linux内核线程实现原理

创建线程使用的底层函数和进程一样,都是clone。从内核里看进程和线程是一样的,都有各自不同的PCB,但是PCB中指向内存资源的三级页表是相同的。进程可以蜕变成线程。线程可看做寄存器和栈的集合。
三级映射:进程PCB --> 页目录(可看成数组,首地址位于PCB中) --> 页表 --> 物理页面 --> 内存单元
对于进程来说,相同的地址(同一个虚拟地址)在不同的进程中,反复使用而不冲突。原因是他们虽虚拟址一样,但,页目录、页表、物理页面各不相同。相同的虚拟址,映射到不同的物理页面内存单元,最终访问不同的物理页面。但线程不同!两个线程具有各自独立的PCB,但共享同一个页目录,也就共享同一个页表和物理页面。所以两个PCB共享一个地址空间。
实际上,无论是创建进程的fork,还是创建线程的pthread_create,底层实现都是调用同一个内核函数clone。如果复制对方的地址空间,那么就产出一个“进程”;如果共享对方的地址空间,就产生一个“线程”。
因此:Linux内核是不区分进程和线程的。只在用户层面上进行区分。所以,线程所有操作函数 pthread_* 是库函数,而非系统调用。
在这里插入图片描述

三、创建线程

1、调用函数:
在这里插入图片描述
返回值:成功返回0,失败返回错误码。

2、pthread_create函数
创建一个新线程。其作用对应进程中fork() 函数。Linux环境下,所有线程特点,失败均直接返回错误号。
在这里插入图片描述
参数说明:
(1)thread :
pthread_t 类型的指针,线程创建成功的话,会将分配的线程 ID 添入该指针指向的地址。线程后续的操作将该值作为线程的唯一标识。
(2)attr :
pthread_attr_t 类型,通过该参数可以定制线程属性,比如可以指定新建线程栈空间的大小,调度策略等。如果要创建的线程无特殊要求,该值设置成 NULL,标识采用默认属性。
(3)start_routine :
线程需要执行的函数。创建线程是为了让线程执行特定的任务。线程创建成功之后,该线程就会执行 start_routinue 函数,该函数之于线程,就如同 main 函数之于主线程。
(4)arg :
线程执行 start_routine 函数的参数。当执行函数需要传入多个参数时,线程创建者(一般是主线程)和新建线程约定一个结构体,创建者把信息填入该结构体,再把结构体的指针传给新建线程,新建线程只要解析这个结构体,就能获取到需要的所有参数。
在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。start_routine函数接收一个参数,是通过pthread_create的arg参数传递给它的,该参数的类型为void *,这个指针按什么类型解释由调用者自己定义。start_routine的返回值类型也是void *,这个指针的含义同样由调用者自己定义。start_routine返回时,这个线程就退出了,其它线程可以调用pthread_join得到start_routine的返回值,类似于父进程调用wait(2)得到子进程的退出状态。

3、线程ID:
pthread_create 函数会产生一个 pthread_t 类型的线程 ID,存放在第一个参数指向的空间内。这里的线程 ID 和前面提到的 pid_t 类型的线程 ID 我们该如何去定位或者看待呢?
pid_t 类型的线程 ID :属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来在整个操作系统内唯一标识该线程。
pthread_t 类型的线程 ID :属于NPTL线程库的范畴,线程库的后续操作,就是根据该线程 ID 来操作线程的。对于 Linux 目前使用的 NPTL 实现而言,pthread_t 类型的 ID 本质上是进程地址空间上的一个地址。
(1)pthread_self 函数:可以获取到线程自身的 ID。其作用对应进程中 getpid() 函数。
pthread_t pthread_self(void); 返回值:成功:0; 失败:无!
在这里插入图片描述
线程ID:pthread_t类型,本质:在Linux下为无符号整数(%lu),其他系统中可能是结构体实现。线程ID是进程内部,识别标志。(两个进程间,线程ID允许相同)。注意:不应使用全局变量 pthread_t tid,在子线程中通过pthread_create传出参数来获取线程ID,而应使用pthread_self。
(2)pthread_equal函数:
在同一个线程组内的,线程库提供了接口,可以判断两个线程 ID 是否对应着同一个线程:
在这里插入图片描述
返回值是 0 的时候,表示是同一个线程,非 0 则表示不是同一个线程。

4、pthread_exit函数:
参数value ptr:value ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)。
在这里插入图片描述
pthread exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
调用phread_exit(),他会等待所有其他对等线程(就是同一个进程中的除自身外其他线程)终止,然后再终止主线程和整个进程。

5、clone函数:
虽然在同一程序中创建的GNU / Linux线程是作为单独的进程实现的,但它们共享其虚拟内存空间和其他资源。但是,使用fork创建的子进程可以获取这些项的副本。
Linux clone系统调用是fork和pthread_create的通用形式,它允许调用者指定在调用进程和新创建的进程之间共享哪些资源。
clone()的主要用途是实现线程:在共享内存空间中并发运行的程序中的多个控制线程。与fork()不同,这些调用允许子进程与调用进程共享其执行上下文的一部分,例如内存空间,文件描述符表和信号处理程序表。
在这里插入图片描述
使用clone()创建子进程时,它将执行函数fn(arg)。 fn参数是指向子进程在执行开始时调用的函数的指针。 arg参数传递给fn函数。
child_stack参数指定子进程使用的堆栈的位置。
虽然属于同一进程组的克隆进程可以共享相同的内存空间,但它们不能共享相同的用户堆栈。 因此,clone()调用为每个进程创建单独的堆栈空间
我们可以将clone视为进程和线程之间共享的统一实现。Linux上进程和线程之间的区别是通过将不同的标志传递给克隆来实现的。差异主要在于这个新流程与启动它的流程之间共享的内容。
CLONE_VM(since Linux 2.0):
如果设置了CLONE_VM,则调用进程和子进程在同一内存空间中运行。 特别是,由调用进程或子进程执行的内存写入在另一个进程中也是可见的。
如果未设置CLONE_VM,则子进程在clone()时在调用进程的内存空间的单独副本中运行。 其中一个进程执行的内存写入不会影响另一个进程,就像fork一样。
在没有vm命令行参数的情况下调用时,CLONE_VM标志关闭,父节点的虚拟内存被复制到子节点中。 子节点看到父节点放在buf中的消息,但无论写入buf的是什么,都会进入自己的副本而父节点无法看到它。
但是当传递vm参数时,会设置CLONE_VM并且子任务共享父级的内存。 现在可以从父母那里看到它写入buf的内容。

6、pthread_join函数
阻塞等待线程退出,获取线程退出状态。其作用对应进程中 waitpid() 函数。
int pthread_join(pthread_t thread, void **retval); 成功:0;失败:错误号
参数:thread:线程ID (注意:不是指针);retval:存储线程结束状态。
注意:

  1. 进程中:main返回值、exit参数–>int;等待子进程结束 wait 函数参数–>int *
  2. 线程中:线程主函数返回值、pthread_exit–>void *;等待线程结束 pthread_join 函数参数–>void **
    参数 retval 非空用法:
    调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
    (1)如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。
    (2)如果thread线程被别的线程调用pthread_cancel异常终止掉,retval所指向的单元里存放的是常数PTHREAD_CANCELED。
    (3)如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。
    (4)如果对thread线程的终止状态不感兴趣,可以传NULL给retval参数。

四、线程的优缺点

1、线程的优点:
(1)创建一个新线程的代价要比创建一个新进程小得多,释放成本也更低。因为创建一个进程就意味着要创建PCB,分配虚拟地址空间,页表,物理内存等系统资源,而创建一个线程只需要创建一个PCB(TCB)即可
(2)与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多。进程切换,需要切换对应的虚拟地址空间,更换页表等。过程繁琐。
(3)线程占用的资源要比进程少很多。
(4)能充分利用多处理器的可并行数量。
(5)在等待慢速I/O操作结束的同时,程序可执行其他的计算任务。
(6)计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现 I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。进程可以将线程串行执行变成并行执行。最后汇总,提高效率。但是尽量不要创建太多线程,线程切换也是需要成本的。
2、线程的缺点
(1)性能损失:
一个很少被外部事件阻塞的计算密集型线程往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
(2)健壮性降低:
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。一个线程挂掉,因为线程共享一块资源。其他线程也会挂掉。进而导致进程退出,资源被回收。
(3)缺乏访问控制:
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。多线程访问临界资源,它的访问控制是由编程者决定。
(4)编程难度提高
编写与调试一个多线程程序比单线程程序困难得多,基于同步互斥。你需要不断的加锁。


http://chatgpt.dhexx.cn/article/MgNooUTb.shtml

相关文章

Linux 线程———详解

1、线程的概念 和 基础知识 1.1 什么是线程 线程可看作轻量级进程(light weight process),Linux的线程本质仍然是进程。Linux先有进程后有线程,当创建了一个进程时,系统给他分配一段4G的虚拟内存,并在其内…

【Linux】线程

前言 目录 1.Linux下的线程概念 2.Linux线程控制:pthread线程库 在单执行流的进程中,此执行流独占了进程的所有资源 在一个进程内部,有时不一定只有一个执行流,在多执行流下,多个执行流共享了进程的地址空间&#xf…

C语言string库strcpy、strcmp、strcat函数详解

strcpy 即string copy 语法格式为strcpy(str1, str2), 作用是将str2赋值给str1 使用方法类似于 char str1[10], str2[] "abc"; strcpy(str1, "bcd"); strcpy(str1, str2); printf("%s", str1); // abcstr2可以是字符串, 也可以是字…

C语言strcpy、strncpy函数是否会复制‘\0‘结束符

1.首先看看strcpy函数的原型 /* strcpy函数原型*/ char *strcpy(char *est,const char *src) {assert((dest!NULL)&&(src!NULL));char *addressdest;while((*dest *src)!\0);//直到src字符串结束符\0return address; }从while循环条…

C语言中的复制函数(strcpy和memcpy)

strcpy和strncpy函数 这个不陌生,大一学C语言讲过,其一般形式为strcpy(字符数组1,字符串2)作用是将字符串2复制到字符数组1中去。 EX: char str1[10],str2[]{"China"}; strcpy(str1,str2); st…

C语言进阶——字符串函数2:strcpy函数

strcpy函数的理解 strcpy函数其实是一种替换函数,用arr2中的元素去替换arr1中的元素,如果不够的话就会补上’\0’ strcpy函数的数据类型 char* strcpy(char* destination,const char* source);这个函数的两个参数分别是拷贝的终点和拷贝的源头 strcp…

c语言 strcpy作用,c语言中的strcpy什么意思,简单点解释

strcpy是一个C语言的标准库函数,是string copy(字符串复制)的缩写。strcpy函数的作用是把含有\0结束符的字符串复制到另一个地址空间,返回值的类型为char*。 扩展资料: C语言中,strcpy 函数不对数组边界进行检查,因而在…

strcpy函数的作用是什么

strcpy函数的作用是复制字符串。C 库函数 char *strcpy(char *dest, const char *src) 把 src 所指向的字符串复制到 dest。需要注意的是如果目标数组 dest 不够大,而源字符串的长度又太长,可能会造成缓冲溢出的情况。声明下面是 strcpy() 函数的声明。c…

C语言strcpy()函数,字符数组复制

需包含头文件&#xff1a;C 标准库 - <string.h> 文章目录 描述声明参数返回值实例实例 1实例 2 20220511 描述 C 库函数 char *strcpy(char *dest, const char *src) 把 src 所指向的字符串复制到 dest。 需要注意的是如果目标数组 dest 不够大&#xff0c;而源字符串…

C++ strcpy_s和strncpy_s使用方法

strcpy_s 函数说明 1、头文件 #include <string.h>2、函数声明 errno_t __cdecl strcpy_s(_Out_writes_z_(_SizeInBytes) char* _Destination,_In_ rsize_t _SizeInBytes,_In_z_ char const* _Source);3、函…

C语言strcpy()函数

前言&#xff1a; strcpy函数的作用是把含有转义字符\0即空字符作为结束符&#xff0c;然后把src该字符串复制到dest&#xff0c;且返回值的类型为“char*”&#xff1b;strcpy是“string copy”&#xff08;字符串复制&#xff09;的缩写。 char *strcpy(char *dest, const ch…

C语言strcpy函数的使用

点击蓝字 关注我们 strcpy简单使用&#xff1a; #include <stdio.h> #include <string.h>struct Student {int sid;char name[20];int age;} st; //此处分号不可省略int main(void) {struct Student st {1000,"zhangsan",20};printf("%d %s %d…

strcpy函数详解看这一篇就够了-C语言(函数实现、使用用法举例、与strncpy的区别)

首先要明确strcpy的功能是&#xff1a;将参数src字符串拷贝至参数dest所指的地址 目录 函数介绍 与strncpy的区别 函数实现1&#xff1a; 函数实现2 改进&#xff1a; 用法示例&#xff1a; 函数介绍 函数原型&#xff1a; char*strcpy&#xff08;char*dest&#xff0c;c…

【C语言】strcpy()函数

文章目录 一、strcpy()函数的简介 二、strcpy()函数的具体使用 三、使用strcpy()函数的注意事项 一、strcpy()函数的简介 strcpy()函数&#xff1a;是将一个字符串复制到另一块空间地址中 的函数&#xff0c;‘\0’是停止拷贝的终止条件&#xff0c;同时也会将 \0 也复制到目…

Sqlserver 查询数据过滤重复,只获取最新一条数据

最近在项目中有这么一个需求&#xff0c;需要从用户提交多条意见中筛选出最新一条数据。因此打算将该语句记录一下。 表结构如下&#xff1a; 普通查询语句&#xff1a; SELECT * FROM T_Dat_OpinionCollection 结果如下&#xff1a; 查询sql&#xff1a; SELECT * FROM (sel…

Kettle(数据过滤)

Kettle(过滤) 原始数据 操作 结果数据

数据预处理之方差过滤

数据预处理之方差过滤 在机器学习的数据预处理的过程中常常会是使用到过滤法&#xff0c;而方差过滤是过滤法之一。所谓的方差过滤就是过滤掉那些特征方差较小的特征。比如一个特征本身的方差很小&#xff0c;就表示样本在这个特征上基本没有差异&#xff0c;可能特征中的大多…

如何使用MyBatis的plugin插件实现多租户的数据过滤?

如何实现多租户数据隔离 在中台服务或者saas服务中&#xff0c;当多租户入驻时&#xff0c;如何保证不同租户的数据隔离性呢&#xff1f;通常的解决方法有三种&#xff0c;分别如下&#xff1a; 一个租户一个独立数据库&#xff0c;这种方案的用户数据隔离级别最高&#xff0…

多种数据过滤与降维算法

数据过滤 1. 缺失值比率 (Missing Values Ratio) 方法的是基于包含太多缺失值的数据列包含有用信息的可能性较少。因此&#xff0c;可以将数据列缺失值大于某个阈值的列去掉。阈值越高&#xff0c;降维方法更为积极&#xff0c;即降维越少。 2. 低方差滤波 (Low Variance Filte…

QT重写QSortFilterProxyModel实现多列数据过滤

在QTableView中可以使用QSortFilterProxyModel来过滤model的数据&#xff0c;可以通过setFilterKeyColumn(int colnum)设置需要过滤的列&#xff0c;当设置为-1时则过滤对象为所有列&#xff0c;但是此种方式只要某一列中有符合条件的数据就会被显示出来 因此我们可以重写QSort…