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

文章目录

  • 前言
  • 一、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相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Flutter的状态管理之Provider

Provider简介 Flutter Provider是Flutter中一个非常流行的状态管理库&#xff0c;它可以帮助开发者更加方便地管理应用程序中的状态。Provider提供了一种简单的方式来共享和管理应用程序中的数据&#xff0c;并且可以根据数据的变化来自动更新UI界面。 Provider的核心思想是将…

C# 自动更新(基于FTP)

效果 启动软件后&#xff0c;会自动读取所有的 FTP 服务器文件&#xff0c;然后读取本地需要更新的目录&#xff0c;进行匹配&#xff0c;将 FTP 服务器的文件同步到本地 Winform 界面 一、前言 在去年&#xff0c;我写了一个 C# 版本的自动更新&#xff0c;这个是根据配置文…

qt学习——基本使用、对象树、按钮、信号与槽

初识qt **qt****qt命名规范以及相关快捷键的使用****QPushButton****对象树****点击按钮关闭窗口****信号和槽****标准的信号和槽****自定义信号和槽****带参数的自定义信号和槽传参以及函数的二义性问题****信号和槽的拓展****qt4的信号与槽****QDebug的输出转义问题****lambd…

STM32 Proteus仿真自动刹车系统超声波测距电机控制-0042

STM32 Proteus仿真自动刹车系统超声波测距电机控制-0042 Proteus仿真小实验&#xff1a; STM32 Proteus仿真自动刹车系统超声波测距电机控制-0042 功能&#xff1a; 硬件组成&#xff1a;STM32F103C6单片机 LCD1602显示器HCSR04超声波传感器按键(加 减)电机蜂鸣器 1.单片机…

Qt编写视频监控系统76-Onvif跨网段组播搜索和单播搜索的实现

一、前言 在视频监控行业一般会用国际onvif工具来测试设备是否支持onvif协议&#xff0c;工具的名字叫ONVIF Device Manager&#xff08;还有个工具叫ONVIF Device Test Tool&#xff0c;专用于程序员测试各种数据交互&#xff09;&#xff0c;可以自行搜索下载&#xff0c;此…

04-编织灵魂旋律:Golang 函数的魔力绽放

&#x1f4c3;个人主页&#xff1a;个人主页 &#x1f525;系列专栏&#xff1a;Golang基础 &#x1f4ac;Go&#xff08;又称Golang&#xff09;是由Google开发的开源编程语言。它结合了静态类型的安全性和动态语言的灵活性&#xff0c;拥有高效的并发编程能力和简洁的语法。G…

通过共享内存进行通信(嵌入式学习)

通过共享内存进行通信 概念特点函数示例代码 概念 在Linux中&#xff0c;共享内存是一种进程间通信&#xff08;IPC&#xff09;机制&#xff0c;允许多个进程共享同一块内存区域。这种通信方式可以提供高效的数据传输&#xff0c;特别适用于需要频繁交换数据的场景。 IO间进…

为CentOs配置静态IP

目录 第一步&#xff1a;查看物理机IP 第二步&#xff1a;虚拟机网络设置 点击虚拟机->编辑虚拟机设置 第三步&#xff1a;CentOS网络配置文件 第四步&#xff1a;重启网络 第五步&#xff1a;测试网络 为什么要设置静态IP 在安装好CentOS虚拟机以后&#xff0c;一般我…

程序替换原理

文章目录 一、程序替换 一、程序替换 程序替换用于将当前进程的用户空间的代码和数据全部替换为新程序的代码和数据&#xff0c;程序替换不会创建新进程&#xff0c;而是用当前进程执行新程序的代码&#xff0c;fork 创建子进程后&#xff0c;子进程默认执行的是父进程的代码&…

vue2和vue3的渲染过程简述版

文章目录 vue2渲染过程vue3渲染过程优化和扩充 vue2和vue3对比 vue2渲染过程 在Vue 2的渲染过程中&#xff0c;包括以下几个关键步骤&#xff1a; 解析模板&#xff1a;Vue 2使用基于HTML语法的模板&#xff0c;首先会将模板解析成抽象语法树&#xff08;AST&#xff09;&…

K8s 部署 Apache Kudu 集群

一、K8s 部署 Apache Kudu 集群 安装规划 组件replicaskudu-master3kudu-tserver3 1. 创建命名空间 vi kudu-ns.yamlapiVersion: v1 kind: Namespace metadata:name: apache-kudulabels:name: apache-kudukubectl apply -f kudu-ns.yaml查看命名空间&#xff1a; kubectl …

JUC高级-0614

5.LockSupport与线程中断 5.1 线程中断 蚂蚁金服面试题&#xff1a;如何中等一个线程&#xff0c;如何停止一个线程什么是中断机制 首先&#xff1a;一个线程不应该由其他线程来强制中断或停止&#xff0c;而是应该由线程自己自行停止。所以&#xff0c;Thread.stop, Thread.…

【spring源码系列-06】refresh中obtainFreshBeanFactory方法的执行流程

Spring源码系列整体栏目 内容链接地址【一】spring源码整体概述https://blog.csdn.net/zhenghuishengq/article/details/130940885【二】通过refresh方法剖析IOC的整体流程https://blog.csdn.net/zhenghuishengq/article/details/131003428【三】xml配置文件启动spring时refres…

十个实用MySQL函数

函数 0. 显示当前时间 命令&#xff1a;。 作用: 显示当前时间。 应用场景: 创建时间&#xff0c;修改时间等默认值。 例子&#xff1a; 1. 字符长度 命令&#xff1a;。 作用: 显示指定字符长度。 应用场景: 查看字符长度时。 例子&#xff1a; 2. 日期格式化 命令…

面试---简历

项目 1.1、商品管理 新增商品 同时插入商品对应的使用时间数据&#xff0c;需要操作两张表&#xff1a;product&#xff0c;product_usetime。在productService接口中定义save方法&#xff0c;该方法接受一张Dto对象&#xff0c;dto对象继承自product类&#xff0c;并将prod…

Stable Diffusion webui 基础参数学习

哈喽&#xff0c;各位小伙伴们大家好&#xff0c;最近一直再研究人工智能类的生产力&#xff0c;不得不说随着时代科技的进步让人工智能也得到了突破性的发展。而小编前段时间玩画画也是玩的不可自拔&#xff0c;你能想想得到&#xff0c;一个完全不会画画的有一天也能创作出绘…

【测试基础01】

软件测试 一、软件测试基本概念(1)、软件测试的定义(2)、软件错误的定义(3)、测试分类 二、需求文档的评审三、软件测试计划(1)、测试范围(2)、测试环境(3)、测试策略(4)、测试管理(5)、测试风险(6)、模板 一、软件测试基本概念 (1)、软件测试的定义 软件测试是从前期需求文档…

freeswitch 使用 silero-vad 静音拆分使用 fastasr 识别

silero-vad 在git 的评分挺高的测试好像比webrtc vad好下面测试下 silero-vad 支持c 和py 由于识别c的框架少下面使用py 以下基于python3.8torch1.12.0torchaudio 1.12.0 1.由于fastasr 需要16k 所以 将freeswitch的实时音频mediabug 8k转成16k 用socket传到py 模块代码…

ChatGLM-6B 在 ModelWhale和本地 平台的部署与微调教程

ChatGLM-6B 在 ModelWhale 平台的部署与微调教程 工作台 - Heywhale.com ChatGLM-6B 介绍 ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM) 架构&#xff0c;具有 62 亿参数。结合模型量化技术&#xff0c;用户可以在消费…

高压放大器在介电材料测试中的应用

介电材料测试是一项重要的材料性能测试&#xff0c;它涉及到物理学、化学、材料科学等多个学科领域。高压放大器是介电材料测试中的一种重要设备&#xff0c;它可以放大微弱的电信号&#xff0c;提高测试的准确性和精度。下面将详细介绍高压放大器在介电材料测试中的应用。 图&…