Linux之多线程(下)——线程控制

news2024/11/26 12:17:44

文章目录

  • 前言
  • 一、POSIX线程库
    • 1.概念
    • 2.pthread线程库是应用层的原生线程库
    • 3.错误的检查
  • 二、线程控制
    • 1.创建线程——pthread_create
      • pthread_create函数
      • 例子
        • 创建一个新线程
        • 主线程创建一批新线程
    • 2.获取线程ID——pthread_self
    • 3.线程等待——pthread_join
    • 4.线程终止——return、pthread_exit、pthread_cancel
      • return
      • pthread_exit
      • pthread_cancel
    • 5.分离线程——pthread_detach
      • pthread_detach函数
      • 例子
  • 总结


前言

本文介绍了Linux下的线程控制。


一、POSIX线程库

1.概念

与线程有关的函数构成了一个完整的系列,大多数函数名都是以“pthread_”为开头的,要使用这些函数需要引入头文件pthread.h。链接这些线程函数库需要使用编译器命令的-lpthread选项。

2.pthread线程库是应用层的原生线程库

我们在Linux之多线程(上)这篇文章中了解:在Linux中没有真正意义上的线程,因此系统无法直接给我们提供创建线程的系统接口,只能提供创建轻量级进程额度接口。
在用户角度,当我们想创建一个线程时会想使用thread_sreate这样的接口,而不是像vfork这样的函数。用户不能直接访问OS,所以OS在用户和系统调用之间提供了编写好的用户级线程库,这个库一般称为pthread库。任何Linux操作下系统都必须默认携带这个库,因此这个库也称为原生线程库。
原生线程库本质上是对轻量级进程的系统调用(clone)做了封装——pthread_create,用户层也因此模拟实现了一套线程相关的接口。
用户眼中的线程实际上会在OS内部被转化为轻量级进程。

3.错误的检查

传统的函数,成功就返回0,失败返回-1,并且给全局变量errno赋错误码以指示错误。
pthread函数出错时并不会设置全局变量errno(大部分其他POSIX函数会设置),而是讲错误码通过返回值返回。当然,pthread函数是提供了线程内的errno变量,以支持其他使用errno的代码。对于pthread函数的错误,建议通过返回值判定,因为读取返回值比读取线程内的errno变量的开销更小

二、线程控制

1.创建线程——pthread_create

pthread_create函数

在这里插入图片描述
参数:

  1. thread:获取线程的ID,该参数是输出型参数;
  2. attr:用于设置创建线程的属性,传入nullptr表示默认,这个属性一般不用管直接传nullptr就行;
  3. start_routine:函数地址,表示线程启动后要执行的函数;
  4. arg:传给线程例程的参数。

返回值:
成功返回0,失败返回错误码。

例子

创建一个新线程

文件mythread.cc

  1 #include<iostream>
  2 #include<string>
  3 #include<unistd.h>
  4 #include<pthread.h>
  5 #include<assert.h>
  6 using namespace std;
  7 void* thread_routine(void* args)
  8 {
  9         string name = static_cast<const char*>(args);//安全的进行强制类型转换
 10         while(1)
 11         {
 12                 cout<<"这是新线程, name:"<<name<<endl;
 13                 sleep(1);
 14         }
 15 }
 16 int main()
 17 {
 18         pthread_t id;
 19         int n = pthread_create(&id, nullptr, thread_routine, (void*)"thread new");
 20         assert(n == 0);
 21         (void)n;
 22         while(1)
 23         {
 24                 cout<<"我是主线程,我正在运行"<<endl;
 25                 sleep(1);
 26         }
 27         return 0;
 28 }

在这里插入图片描述
这里编译运行需要注意的是,pthread_create接口是库提供给我们的,我们使用的接口如果不是语言上的接口或者操作系统的接口,而是库提供的接口,那么在编译的时候是无法通过的,需要链接这个库才能编译成功。要链接这个库首先要找到这个库,-L:找到库在哪里;-l:找到头文件在哪里,库已经在系统中安装好了,所以除了高所系统库和头文件在哪里以外,还要知道是链接哪一个库(库名字)。
所以要加上-lpthread
此时我们用ps axj命令查看当前进程的信息时,虽然此时该进程中有两个线程,但是我们只能看到一个进程,因为这两个线程是属于一个进程的:

要想查看到轻量级进程需要使用ps -aL指令:

其中LWP(Light Weight Process)表示的是轻量级进程的ID,可以看到显示出的两个轻量级进程的PID是相同的(因为它们属于同一个进程),而每个轻量级进程都有唯一的LWP。
注意:主线程的PID和LWP是相同的,PID和LWP不相同的是新线程,所以CPU进行调度时,是以LWP为标识符进行标定一个线程执行流。
线程一旦被创建,几乎所有的资源都是被所有线程所共享的,因此线程之间想要进行交互是很容易的,因为直接就可以看到同一份资源。

线程要有自己的私有资源:
线程被调度就要有独立的PCB属性——LWP;
线程被切换时正在运行,需要进行上下文的保存,因此线程要有私有的上下文结构;
每个线程都要独立的运行,所以线程要有自己独立额度栈结构。

主线程创建一批新线程

让主线程一次性创建十个新线程,并让创建的每个新线程都去执行start_routine函数,即start_routine这个函数会被重复进入。并且start_routine函数是可重入函数(不会产生二义性),没有因为一个线程去影响另一个线程。在函数定义内定义的变量都是局部变量具有临时性,所以在多线程的情况下也没有问题。
文件mythread.cc

这也说明了每个线程都有自己独立的栈结构

2.获取线程ID——pthread_self

在这里插入图片描述
获取线程ID:1.创建线程时通过输出型参数获取;2.通过pthread_self接口函数获取。
我们可以通过主线程打印出新线程的ID,再通过新线程打印出自己的ID,判断是否相同。

结果是相同的。

3.线程等待——pthread_join

一个线程退出时和进程一样是需要等待的,如果线程不等待,对应的PCB没有被释放也会造成类似僵尸进程的问题(内存泄漏)。所以线程也需要被等待:1.获取新线程的退出信息;2.回收新线程对应的PCB等内核资源,防止内存泄漏。
在这里插入图片描述
参数:
thread是被都能打线程的ID;
retval:线程退出时的退出码。

void** retval:输出型参数,主要用于获取线程退出时返回的退出结果。之所以是void**,是因为如果想作为输出型结果返回就必须是void**(因为线程函数的返回结果是void*)

返回值:线程等待成功返回0,等待失败返回错误码。

没有看到线程退出时对应的退出码是因为线程出异常时收到信号,整个进程都会退出,而退出信息需要进程来关心,所以pthread_join默认会认为函数是调用成功的(等待成功),它不会考虑程序出现异常的情况,异常问题是进程该考虑的情况。

4.线程终止——return、pthread_exit、pthread_cancel

一个线程,如果只是想终止该线程而不是整个进程,有三种做法

  1. 直接从线程的函数结束,return就可以终止该线程;
  2. 线程可以自己调用pthread_exit终止自己;
  3. 一个线程可以调用pthread_cancel来终止同一个进程中的另一个线程。

return

pthread_exit

在这里插入图片描述

pthread_cancel

在这里插入图片描述

5.分离线程——pthread_detach

线程是可以等待的,等待的时候是join的等待(阻塞式等待)。如果我们不想等待:不去等待线程,而是进行分离线程处理。默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放该线程的资源,造成内存泄漏。
如果我们并不关心线程的返回值,此时join对我们来说是一种负担,这时,我们可以告诉OS,当线程退出时,自动释放线程资源,这种策略就是线程分离

pthread_detach函数

在这里插入图片描述

例子

创建新线程,让主线程与新线程运行起来,主线程等待新线程退出,等待完毕返回n。由于我们现在让新线程进行分离,那么按照理论此时主线程的等待结果是失败的。
文件mythread.cc

  1 #include<iostream>
  2 #include<unistd.h>
  3 #include<pthread.h>
  4 #include<string.h>
  5 using namespace std;
  6 #include<string>
  7 string changeld(const pthread_t& thread_id)
  8 {
  9         char tid[128];
 10         snprintf(tid, sizeof(tid), "0x%x", thread_id);
 11         return tid;
 12 }
 13 void* start_routine(void* args)
 14 {
 15         string name = static_cast<const char*>(args);
 16         pthread_detach(pthread_self());//线程分离,设置为分离状态
 17         int cnt = 5;
 18         while(cnt--)
 19         {
 20                 cout<<name<<"is running..."<<changeld(pthread_self())<<endl;
 21                 sleep(1);
 22         }
 23         return nullptr;
 24 }
 25 int main()
 26 {
 27         pthread_t tid;
 28         pthread_create(&tid, nullptr, start_routine, (void*)"thread 1");
 29         string main_id = changeld(pthread_self());
 30         cout<<"main thread running... new thread id:"<<changeld(tid)<<"main thread id:"<<main_id<<endl;
 31         int n = pthread_join(tid, nullptr);
 32         cout<<"result:"<<n<<":"<<strerror(n)<<endl;
 33         return 0;
 34 }

运行:
在这里插入图片描述
但是我们发现等待结果依旧是成功的,这是为什么?
因为,我们创建新线程后,并不确定新线程和主线程哪个先被调度,所以可能导致我们还没有执行新线程的pthread_detach时,主线程就去等待新线程了。也就是说,新线程还没有来得及分离自己,主线程就去等待了。
因此我们可以让主线程sleep(2),保证新线程是分离的状态主线程再去等待,则此时等待是失败的。
文件mythread.c

  1 #include<iostream>
  2 #include<unistd.h>
  3 #include<pthread.h>
  4 #include<string.h>
  5 using namespace std;
  6 #include<string>
  7 string changeld(const pthread_t& thread_id)
  8 {
  9         char tid[128];
 10         snprintf(tid, sizeof(tid), "0x%x", thread_id);
 11         return tid;
 12 }
 13 void* start_routine(void* args)
 14 {
 15         string name = static_cast<const char*>(args);
 16         pthread_detach(pthread_self());//线程分离,设置为分离状态
 17         int cnt = 5;
 18         while(cnt--)
 19         {
 20                 cout<<name<<"is running..."<<changeld(pthread_self())<<endl;
 21                 sleep(1);
 22         }
 23         return nullptr;
 24 }
 25 int main()
 26 {
 27         pthread_t tid;
 28         pthread_create(&tid, nullptr, start_routine, (void*)"thread 1");
 29         string main_id = changeld(pthread_self());
 30         cout<<"main thread running... new thread id:"<<changeld(tid)<<"main thread id:"<<main_id<<endl;
 31         sleep(2);
 32         int n = pthread_join(tid, nullptr);
 33         cout<<"result:"<<n<<":"<<strerror(n)<<endl;
 34         return 0;
 35 }

运行:
在这里插入图片描述

当然,我们也可以直接让主线程直接pthread_detach,而不是让新线程分离:线程运行起来就直接分离了,分离成功就去join了,此时新线程就去等待了。
文件mythread.c

  1 #include<iostream>
  2 #include<unistd.h>
  3 #include<pthread.h>
  4 #include<string.h>
  5 using namespace std;
  6 #include<string>
  7 string changeld(const pthread_t& thread_id)
  8 {
  9         char tid[128];
 10         snprintf(tid, sizeof(tid), "0x%x", thread_id);
 11         return tid;
 12 }
 13 void* start_routine(void* args)
 14 {
 15         string name = static_cast<const char*>(args);
 16         int cnt = 5;
 17         while(cnt--)
 18         {
 19                 cout<<name<<"is running..."<<changeld(pthread_self())<<endl;
 20                 sleep(1);
 21         }
 22         return nullptr;
 23 }
 24 int main()
 25 {
 26         pthread_t tid;
 27         pthread_create(&tid, nullptr, start_routine, (void*)"thread 1");
 28         string main_id = changeld(pthread_self());
 29         pthread_detach(tid);//让主线程线程分离
 30         cout<<"main thread running... new thread id:"<<changeld(tid)<<"main thread id:"<<main_id<<endl;
 31         int n = pthread_join(tid, nullptr);
 32         cout<<"result:"<<n<<":"<<strerror(n)<<endl;
 33         return 0;
 34 }

在这里插入图片描述


总结

以上就是今天要讲的内容,本文介绍了线程控制相关的概念。本文作者目前也是正在学习Linux相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/651398.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SSH服务器详解

文章目录 文字接口连接服务器&#xff1a;SSH服务器连接加密技术简介启动SSH服务SSH客户端连接程序SSH&#xff1a;直接登录远程主机的指令使用案例 服务器公钥记录文件&#xff1a;~/.ssh/known_hosts报错解决 模拟FTP的文件传输方式&#xff1a;SFTP使用案例 文件异地直接复制…

Python主要应用的10大领域你是否感兴趣

原文&#xff1a; Python主要应用的10大领域你是否感兴趣 Python 是一门快速发展的编程语言&#xff0c;其在各个领域的应用也在不断增加。根据 TIOBE 编程语言排行榜&#xff0c;Python 在 2021 年排名第 3&#xff0c;仅次于 Java 和 C。根据 Stack Overflow 的开发者调查报…

圆的基本性质

如何确定一个圆&#xff1f; 两个点&#xff1a; 无法确定一个圆&#xff0c;因为只要到这两个点距离相等的点都可以作为圆心&#xff08;在两个点连线的垂直平分线上&#xff09;&#xff0c;因此可以确定无数个圆 三个点&#xff08;且这三个点不能在同一个直线上&#xf…

【MySQL数据库】事务

事务 一、事务1.1事务的概念 二 、事务的ACID特点2.1原子性2.2一致性&#xff08;Consistency&#xff09;2.3隔离性2.4持久性 三、脏读、不可重复读、幻读、丢失更新3.1脏读3.2不可重复读3.3幻读3.4丢失更新 四、事务的隔离级别 一、事务 1.1事务的概念 事务是一种机制、一个…

二叉堆(大顶堆、小顶堆)学习(使用java手写)

二叉堆 我们现在有一个需求&#xff0c;用来存放整数&#xff0c;要求需要提供三个接口 添加元素获取最大值删除最大值 我们可以用我们熟悉的数据结构去解决这些问题 获取最大值删除最大值添加元素描述动态数组/双向链表O(n)O(n)O(1)O(n) 复杂度太高了&#xff08;有序&#x…

redis -- 持久化存储方案

前言 一般情况下&#xff0c;我们存储到redis的数据&#xff0c;是存储到内存中&#xff0c;再存储到硬盘中(这是基于reb方案来实现)因此一旦强制关机,就直接over了。 硬存和内存的区别和联系&#xff1a; 我们用文本编辑器&#xff0c;里面写入一段话&#xff0c;未保存&am…

检测PPG信号的心跳

基于大佬的代码。 PPG信号靠心率 (HR) 进行估计&#xff0c;主要取决于收缩压峰值检测的准确性。与 ECG 不同&#xff0c;PPG 信号形式简单和特定点 少。低振幅 PPG 信号更容易受到噪声污染和其他不良影响的影响&#xff0c;例如baseline drift和wandering。这是由于信号强度与…

从零开始理解Linux中断架构(3)--Armv8体系架构

首先让我们带着问题进入到armv8架构的学习中。linux中断代码分为两部分entry.S @arch\arm64\kernel\entry.S汇编部分和C代码后续处理。汇编代码中处理最为低级的部分,设置硬件中断向量表,保持当前上下文,切换中断堆栈等任务,这是就如我们嵌入式系统看到那样。 @arch\arm64…

Vue3中div自由拖拽宽度和高度。

Vue3中我们会遇到自由拖拽宽度和高度的页面需求&#xff0c;查看很多方法都无法满足当前需求。下面是我们Vue3版本的代码&#xff0c;非常简单主要构想说粗发拖拽方法&#xff0c;把所需要的div的高宽进行拖拽位置进行监听来加减自身div的px值。直接复制粘贴就可以实现效果。根…

20230615整理(字符设备驱动的内部实现)

1.1 字符设备&#xff1a; 以字节流的形式进行访问&#xff0c;而且只能顺序访问的设备叫做字符设备(比如键盘、鼠标) (块设备&#xff1a;有固定访问大小&#xff0c;可以不按顺序访问的设备&#xff0c;比如U盘、硬盘) 针对字符设备编写的驱动叫做字符设备驱动 1.2 当设备驱…

AI实战营:通用视觉框架OpenMMLab底层视觉与MMEditing

目录 图像超分辨率 Super Resolution ​​​ 深度学习时代的超分辨率算法 卷积网络模型SRCNN FSRCNN SRResNet Super-Resolution CNN, SRCNN, 2014 Fast SRCNN 2016 SRResNet 2016 对抗生成网络介绍Ganerative Adversarial Network 基于GAN的模型SRGAN与ESRGAN S…

vite+vue3+ts 报错和解决办法汇总

1. import path from path 时 ts 报错&#xff1a;模块 ""path"" 只能在使用 "allowSyntheticDefaultImports" 标志时进行默认导入。 在 tsconfig.node.json 文件的 compilerOptions 添加配置 "allowSyntheticDefaultImports": true …

【Python】Django 基础知识 一

系列文章目录 提示&#xff1a;阅读本章之前&#xff0c;请先阅读目录 文章目录 系列文章目录前言安装启动项目查看所有子命令主要文件setting 配置项URL 请求路径path 转换器HttpResponse 输出中文乱码 前言 安装 django-admin startproject xxx项目名启动项目 python manag…

ffmpeg 3.4 windows编译安装

准备工作: msys2安装 官网 MSYS2 下载完成后一直下一步即可&#xff0c;安装完成后windows搜索 MSYS2 启动MSYS2 MINGW64 打开窗口后运行以下命令 下载一些编译需要的东西 #修改源 sed -i "s#mirror.msys2.org/#mirrors.ustc.edu.cn/msys2/#g" /etc/pacman.d/mirr…

拔剑四顾心茫然,绿源直呼“行路难”

老牌两轮电动车品牌绿源上市之旅“多歧路”。 6月7日&#xff0c;北京市市场监督管理局公布北京市电动自行车产品质量监督抽查结果&#xff0c;绿源两款电动自行车因存在问题被点名&#xff0c;充电器和蓄电池、整车质量、控制系统等不符合标准。 而就在一周多以前&#xff0c…

指针(四)

文章内容&#xff1a; 1. 数组参数、指针参数 2. 函数指针 3. 函数指针数组 4. 指向函数指针数组的指针 5. 回调函数 文章内容 1. 数组参数、指针参数 地址需要指针接收&#xff0c;指针的地址需要二级指针接受,以此类推...... 1.1 一维数组传参 #include <stdio.…

【思考】技术人该如何准备晋升答辩?

文章目录 前言一、争取获得答辩机会二、准备答辩素材三、根据素材&#xff0c;编写答辩 PPT四、晋升答辩素材 PPT 的一些建议五、要写答辩稿并加以练习六、调整答辩心态总结 前言 今天跟大家聊下关于技术人该如何准备晋升答辩的话题。 每到年中或者年底&#xff0c;都会有一波…

微前端探秘:初始微前端、现有方案和未来趋势

初识微前端 微前端是什么 概念&#xff1a; 微前端是指存在于浏览器中的微服务。 微前端是一种类似于微服务的架构&#xff0c;它将微服务的理念应用于浏览器端&#xff0c;即将单页面前端应用由单一的单体应用转变为把多个小型前端应用聚合为一体的应用。这就意味着前端应用…

OpenMMLab-AI实战营第二期——6-1. 底层视觉与MMEditing

文章目录 1. 图像超分辨率&#xff08;Super Resolution&#xff09;1.1 概念1.2 目标1.3 应用1.4 分类1.4-2 单图超分的解决思路1.5 ill-posed problem和算子 2. 经典超分辨率算法&#xff08;稀疏编码Sparse Coding&#xff09;2.1 基本概念2.2 基本流程2.3 缺点 3. 深度学习…

【Java项目】1000w数据量的表如何做到快速的关键字检索?

文章目录 需求解决思路基本设计查询流程插入流程修改流程删除流程 优化思路代码实现 需求 ok&#xff0c;这个需求是我提的&#xff0c;然后我问了我的一位杭州的朋友&#xff0c;然后我们最后一起敲定这个方法。 我的项目有一个根据关键字进行商品名称的搜索功能&#xff0c…