【Linux】线程

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

前言

目录
1.Linux下的线程概念
2.Linux线程控制:pthread线程库
在单执行流的进程中,此执行流独占了进程的所有资源

在一个进程内部,有时不一定只有一个执行流,在多执行流下,多个执行流共享了进程的地址空间,我们把“一个程序内部的控制序列”叫做线程

线程本质是在进程的地址空间内运行
进程的切换涉及到页表映射的切换,而线程的切换只是切换了指令序列而在同一个地址空间中进行

那么我们给出下面两个重要概念

  • 进程是操作系统分配资源的基本实体
  • 线程是进程内部的一个执行流

举个栗子:在这里插入图片描述
在现实生活中,假如我们把社会资源的基本单位看作是家庭,比如我们经常以家庭年收入统计社会财富的分配状况,那么此时:

  • 操作系统—>社会
  • 进程—>家庭
  • 线程—>家庭成员

家庭成员共享了家庭的资源,家庭成员之间有共享的资源,也有私人的小秘密。

透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流

线程可以被创建、等待、终止、控制
家庭有生老病死、家规等等…

1.Linux下的线程概念

在Linux下,其实没有真正意义上的线程概念,是用进程来模拟的

Linux的进程叫做轻量级进程

LWP是轻量级进程,在Linux下进程是资源分配的基本单位线程是cpu调度的基本单位,而线程使用进程pcb描述实现,操作系统在创建线程时给每个线程都创建一个pcb结构体,并且同一个进程中的所有pcb共用同一个虚拟地址空间,因此相较于传统进程更加的轻量化有了更多执行流之后。进程变成了分配资源的基本实体,进程一旦被创建好之后,里面可能有多个执行流

与进程相比,线程在CPU执行时可能更加轻量化:pcb上下文肯定要切换,但是线程的地址空间、页表不用换CPU调度时,看到的是LWP,也就是轻量级进程Light Weight Process

在这里插入图片描述

1.1 线程的优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要OS的工作量更少
  • 线程占用的资源比进程少得多

1.2 线程能够看到进程的所有资源,因为所有PCB都共享地址空间

  • 好处:线程间通信成本低
  • 坏处:存在大量的临界资源,势必需要使用各种互斥和同步机制保证临界资源的安全

1.3 线程异常

线程是进程的一个执行分支,当发生野指针/除0等异常操作导致线程退出的同时,也意味着进程触发了该错误,操作系统会给进程发送信号,终止进程。这体现了多线程下鲁棒性降低了。

1.4 线程的共享与独享

线程共享

  • 文件描述符表
  • 每种信号的处理方式
  • 工作目录
  • 用户id组id

独有:

  • 上下文数据(寄存器):体现了多个线程是可以切换的
  • 独立的栈结构:体现了线程是独立运行的,各自的上下文数据不会互相影响

2.Linux线程控制:pthread线程库

首先要强调一点:pthread库并不是系统库,而是Linux下为了模拟线程而采用的第三方库。本质是封装了对于轻量级进程的某些操作。

链接这些线程函数库时要使用编译器命令的“-lpthread”选项
接口:
pthread_create()
pthread_join()
pthread_cancel()
pthread_exit()
pthread_self()

2.1线程的创建

功能: 创建一个新的线程
原型

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);

4个参数

  • thread:返回线程ID,这是进程地址空间的共享区的地址
  • attr:设置线程的属性,attr为NULL表示使用默认属性
  • start_routine:是个函数地址,线程启动后要执行的函数
  • arg:传给线程启动函数的参数,如果需要传入多个参数,可以用结构体封装

返回值: 成功返回0;失败返回错误码
在这里插入图片描述

让我们来玩一玩线程的创建,这边我们在main函数,也就是主线程创建了新线程,又在新线程中创建了另外5个更新的线程,使用ps -aL 命令查看轻量级进程

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void* routine(void* args) {while(true) {cout << "I am a thread" <<  endl;sleep(1);}return nullptr;
}
void* ThreadRoute(void* args) {pthread_t tids[5];for(int i = 0; i < 5; i++) {pthread_create(tids+i, nullptr, routine, nullptr);}for(int i = 0; i < 5; i++) {pthread_join(tids[i], nullptr);}return nullptr;
}
int main()
{pthread_t tid; int thread_id = pthread_create(&tid, nullptr, ThreadRoute, (void*)"I am thread");while(true) {std::cout << "I am main process" << std::endl;sleep(2);}return 0;
}

在这里插入图片描述

2.2 线程的终止

  • 从自己的例程中return,线程退出
  • 主线程退出,进程退出

有三种方法终止某个线程而不终止进程:

  1. 从线程函数return这种方法对主线程不适用,从main函数return相当于调用exit。
  2. 线程可以调用pthread_ exit终止自己。
  3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程

线程一般终止之后,main thread等待,不等待会造成僵尸

为防内存泄漏,要保证主线程最后退出,让新线程正常结束

retral从pcb中提取退出码

  • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
  • 创建新的线程不会复用刚才退出线程的地址空间。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
using namespace std;void* ThreadRoute(void* args) {int a = 0;int b = 10;b = b/a;return nullptr;
}int main()
{if(fork() == 0) {pthread_t tid; int thread_id = pthread_create(&tid, nullptr, ThreadRoute, (void*)"I am thread");exit(11);}int status;pid_t id = waitpid(-1, &status, 0);cout << "exit code: " << ((status>>8)&0xff) << endl;cout << "sig: " << ((status)&0x7f) << endl;return 0;
}

我们精心设计了除0错误:
在这里插入图片描述
在进程exit code中,存有退出码+信号,而信号是针对进程的,线程崩溃进程随之崩溃

子进程创建的线程的除0错误导致OS给子进程发送信号,子进程的主线程崩溃,退出码收不到

关于pthread_cancel

  • sleep新线程被创建了但是没有被调度你就cancel了,建议一定要让新线程被调度跑起来
  • cancel具有一定的延时性,并不一定立即执行
  • cancel建议不要再开头或结尾使用

2.3 线程等待

为什么要等待?

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

功能:等待线程结束
原型

int pthread_join(pthread_t thread, void **value_ptr);

参数

  • thread:线程ID
  • value_ptr:它指向一个指针,后者指向线程的返回值

返回值成功返回0;失败返回错误码

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

  1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED。
  3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

我们让线程return一个new出来的5

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
using namespace std;void* ThreadRoute(void* args) {int* p = new int(5);cout << "threadID: " << pthread_self() << endl;sleep(2);return (void*)p;
}
int main()
{  if(fork() == 0) {pthread_t tid; void* ret;int thread_id = pthread_create(&tid, nullptr, ThreadRoute, (void*)"I am thread");pthread_join(tid, &ret);cout << "ret: " << *(int*)ret << endl;delete (int*)ret;exit(11);}int status;pid_t id = waitpid(-1, &status, 0);cout << "exit code: " << ((status>>8)&0xff) << endl;cout << "sig: " << ((status)&0x7f) << endl;return 0;
}

在这里插入图片描述

2.4 获取线程ID

pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。

前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。

pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。

线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:

pthread_t pthread_self(void);
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
using namespace std;void* ThreadRoute(void* args) {cout << "threadID: " << pthread_self() << endl;
}
int main()
{if(fork() == 0) {pthread_t tid; int thread_id = pthread_create(&tid, nullptr, ThreadRoute, (void*)"I am thread");exit(11);}int status;pid_t id = waitpid(-1, &status, 0);return 0;
}

在这里插入图片描述

2.5 线程分离

线程分离的本质是让主线程不join新线程,不关心返回值,从而让新线程退出的时候自动回收

如果一个线程被设置为分离状态,他就不该被join
如果你join,结果就是未定义
即便线程被设置为分离状态,但是如果该线程依旧出错崩溃,还是会影响主线程和其他正常线程 -> 所有线程在同一个地址空间中运行

int pthread_detach(pthread_t thread);

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

pthread_detach(pthread_self());

线程分离后,主线程等待不到新线程:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void* thread_run(void* arg)
{pthread_detach(pthread_self());printf("%s\n", (char*)arg);return NULL;
}
int main(void)
{pthread_t tid;if (pthread_create(&tid, NULL, thread_run, "thread1 run...") != 0) {printf("create thread error\n");return 1;}int ret = 0;sleep(1);//很重要,要让线程先分离,再等待if (pthread_join(tid, NULL) == 0) {printf("pthread wait success\n");ret = 0;}else {printf("pthread wait failed\n");ret = 1;}return ret;
}

在这里插入图片描述

3.总结补充

为什么要有pthread原生线程库?

linux没有真正的线程,是用进程来模拟的

操作系统是不会直接提供类似的线程创建、退出、分离、等待相关的system call 接口,但是会提供创建轻量级进程的接口,但是用户需要有所谓的线程创建、退出、分离、等待相关的接口啊,所以为了更好的适配轻量级进程的接口,就模拟封装了一个用户层原生线程库NPTL

可是进程是PCB去管理的,用户层先要以管理线程的办法来管理轻量级进程,就得知道,线程id,状态,优先级,其他属性,从而用来进行用户线程管理!

所以tcb不用内核维护,而在用户层维护

曾经的pthead_t 是用户层的概念,是pthread库中的地址


相当于警察派了几个线人去犯罪集团当卧底,警察不懂行话,但是线人懂,所以线人充当了和犯罪分子沟通的角色,而线人反手会把情报用人话反馈给警察

警察只需要来操纵、管理线人,就能得到情报


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

相关文章

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可以是字符串&#xff0c; 也可以是字…

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

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

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

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

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

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

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

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

strcpy函数的作用是什么

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

Java流Stream-1:数据过滤filter

重要&#xff1a;默认过滤数据实体对象时为引用传递。 &#xff08;1&#xff09;若修改过滤后的值&#xff0c;原始值会改变。 &#xff08;2&#xff09;若过滤后的数据需要变更&#xff0c;且原始值不受影响&#xff0c;需要过滤时新建对象。 1 filter方法 按照指定条件过…

【Python数据分析与处理 实训02】 ---2012欧洲杯信息分析(数据过滤与排序)

【Python数据分析与处理 实训02】 —2012欧洲杯信息分析&#xff08;数据过滤与排序&#xff09; 探索2012欧洲杯信息 对于下面的数据集进行简单的一些数据的分析训练 若需要源数据请私信~ 读取数据 euro12 pd.read_csv("G:\Projects\pycharmeProject\大数据比赛\泰迪…