Linux——线程2|线程控制

news2025/1/20 18:32:48

什么是线程

  1. 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”。

  1. 一切进程至少都有一个执行线程

  1. 线程在进程内部运行,本质是在进程地址空间内运行

  1. 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化

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

线程的优点

  1. 创建一个新线程的代价要比创建一个新进程小得多

  1. 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多

  1. 线程占用的资源要比进程少很多

  1. 能充分利用多处理器的可并行数量

  1. 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务

  1. 计算密集型(大部分时间线程都在做计算)应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现

  1. I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作

线程的缺点

  1. 性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。

  1. 健壮性降低:编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。

  1. 缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。

  1. 编程难度提高:编写与调试一个多线程程序比单线程程序困难得多

线程异常

单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃

线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该

进程内的所有线程也就随即退出

线程用途

合理的使用多线程,能提高CPU密集型程序的执行效率

合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是

多线程运行的一种表现)

进程和线程的关系如下图:

fork底层调用以clone为主,clone是创建一个轻量级进程,第二个参数是子进程的栈,clone可以设置是否共享地址空间

还有vfork,这也是创建子进程,这个创建的子进程和父进程共享地址空间

当主线程和新线程创建成功后,谁先运行,这个没有标准答案,由调度器决定谁先运行。

让新线程除0,1s后我们发现进程直接退出。因此线程一旦异常,可能会导致整个进程直接退出。

线程在创建并执行的时候,也是需要进行等待的,如果主线程不等待,即会引起类似于进程的将是问题,进而导致内存泄漏。

用pthread_join去进行等待

第一个参数是线程id,第二个后面说。返回值成功0,失败返回错误码。

新线程退出后,主线程等待成功并回收相关资源

进行资源监视,由于我们的程序没有让主线程完成等待后执行其它任务,这里在等待完成后,直接执行到了return 0,但我们可以确定的是子线程被进行了回收。

while :; do ps -aL | head -1 && ps -aL |grep mythread; sleep 1; done

对于创建线程时的传参问题:我们可以像上面程序一样按照类似于 (void*)"thread 1",这个参数是给第二个函数当实参的。对于第二个函数的返回值,如果我们想返回某个值,我们可这样进行强制类型转换。也就是说我们把10当作了一个指针数据,也就是说有一个地址,地址数据是10。

但这里的返回值又是返回给谁呢?一般是给主线程,谁等待给谁。

那主线程又如何获取到这个数据呢?我们这里通过pthread_join进行获取。

pthread_join第二个参数

主线程返回值是void *,所以我们这里要用void * *接收

我们提取传进来的数据,这里我们用ret接收,ret是指针变量大小是void *,void类型大小不明确,但是void *有大小,这里是8个字节。指针就是地址,地址就是一个数据。整整型变量:1.要开辟空间2.这个空间里保存的都是整型,这里ret是一个指针变量,可以去盛装对应的数据,我们把10强转为void*,此时(void*)10就是一个指针数据,ret就是指针对应的空间,有了空间我们就能进行取地址操作。

当我们最后把这个10拿到的时候,由于ret有空间,所以这个10直接保存在ret里面

我们把ret强转成整数,并打印

运行程序,此时报错,这份代码如果在32位机器下可以直接跑过,在64位就跑不过了,因为int是4字节的,而ret在这里是8字节,因此我们强转位long long类型

修改后

此时我们拿到了10

新线程不仅仅可以返回数字,还可以这样操作,若我们想把下面新线程中i的计算结果交给主线程

注意我们这里的ret是int*,在函数当实参的时候转换成了void **

我们看到新线程退出后,主线程拿到了遍历的结果。

终止线程

exit和pthread_exit

从新线程里进行return能终止线程。

我们在新线程中加入exit进行测试。

我们发现新线程退出后,主线程里的消息没有被打印,主线程也跟着直接退出。因此我们在多线程当中不要用exit,exit是终止整个进程的

pthread_exit是线程终止,让线程退出。这个参数也是void*,返回值是void*

此时打印了主线程的语句

pthread_cancel

pthread_cancel线程取消,如果要取消某个线程,发送一个取消请求给目标线程,参数是线程id

我们让新线程陷入死循环,主线程count=5时,退出循环,然后将线程取消,主线程由于有pthread_join所以会等待子线程退出后,继续执行剩下的代码。

我们可以看到线程id特别大,

我们让主线程最后sleep5s

当新线程取消后,主线程执行完自己的代码sleep 5s,我们可以看到这5s期间只有主线程。

对于上面的现象我们可以得出结论:

  1. 一旦线程被取消,join的时候,退出码是-1,-1其实是PTHREAD_CANCELD;即一个线程被取消,该线程的退出码会自动被设置为-1,所以我们会提取到-1

当我们创建线程之后,立马取消,线程卡在了这里,而且始终只有一个线程。这是因为pthread_cancel用法不是这么用的

我们在使用pthread_cancel的时候要首先要保证线程是存在的,并且线程已经彻底运行起来了,而且用主线程去取消新线程。

我们也可以用新线程取消主线程,但不推荐这么干,会有很大麻烦,如果主线程被干掉了,新线程没人处理,而且可能会引起其它问题。这种做法没有啥意义。

线程ID

我们打印线程id,tid打出来这么大,这是因为它本质是一个地址,因为我们目前用的不是Linux自带的创建线程的接口,我们用的是pthread库中的接口。

pthread库加载到内存中,然后通过页表映射到地址空间(共享区)当中,我们自己的代码在代码区当中,当我们调用pthread库时,跳转到共享区中去调库函数。

由于线程在运行时,需要自己的独立栈结构,CPU中有ebp和esp,当有多个线程执行流来回被切换时,如果使用同一个栈,我们压栈,入栈时对应的数据就全部乱起来了,那么如何保证栈区是每一个线程独占的呢?

设计者对于OS的设计思路是不想让OS感知到栈的存在,最多感知到轻量级进程,因此在用户层提供栈,而不是让OS提供栈。

库的设计者提供了给每个线程进行维护的私有数据,包括线程的id,局部存储和线程的栈结构。注意这个栈是由库提供的,在共享区中维护,为了更好的让每个线程找到自己的用户及属性,我们用线程属性集合的起始地址充当线程的tid。

主线程用的是内核级别的栈结构,新线程用的是共享区当中库所提供的栈结构,因此可以保证每个线程都有独立的栈结构。

如何保证,创建线程的时候用共享区的一个地址来充当栈结构?

clone函数,第一个参数是执行方法,第二个参数是轻量级进程在用户层的栈结构(这个栈结构,可以由我们人为指定),其实pthread库底层创建PCB时候调用clone,同时在自己的库内部申请对应的线程相关属性字段,然后把线程的相关地址传入给新线程,新线程在调度的时候,就直接用共享区的栈区。

pthread_self

哪个线程调的pthread_self,就获取当前线程的线程id

我们可以用pthread_cancel和pthread_self组合,让自己取消自己pthread_cancel(pthread_self());但不推荐这种做法,可能会引起一些不必要的麻烦。

证明全局数据被所有线程共享

我们看到新线程和主线程的count是同步变化的,因此可以说明新线程和主线程访问的是同一个全局变量

若想让全局变量称为每个线程内私有的变量我们在变量前面加上__thread

__thread修饰全局变量,带来的结果是让每一个线程各自拥有一个全局的变量,这叫做线程的局部存储。

加了__thread之后,编译的时候给买个线程都拷贝一个g_val。

我们看到主线程打印出来的是0,而新线程的count的值一直在++。

若我们在线程里调用execl()进行程序替换,带来的后果就是将整个代码和数据全部进行替换掉。并且会影响其它线程。

运行结果。

我们先让它sleep5s

进入新线程之后sleep5s,5s后线程全部退出。我们发现线程一旦被替换,整个代码会全部被替换,exec系列是进程替换。

如果在线程里调fork,我们子进程会拷贝主线程PCB。

线程分离

线程在终止时,不想再被等待,我们就可直接进行线程分离。

默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。

如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

主线程可以分离新线程,但是一般都采用线程自己分离自己。

用pthread_deatch进行线程分离

程序会打印错误信息,这是因为我们把线程分离后,无法join,错误原因是非法参数,线程分离之后就不能等了,因为等不到。

若主线程提前退出,就意味着进程退出。线程也会跟着退出。无论是多进程还是多线程,父进程或主线程永远是最后退出。

我们让子线程分离,之后让子线程运行错误程序,整个进程直接结束。因此线程分离,若子线程异常,整个进程都会退出。

C++中有自己对应的线程。我们注释掉makefile里面的-lpthread

程序运行后会对pthread_create进行报错,报错信息为没有该函数。

我们再在makefile里面加上-lpthread

此时C++的线程跑了起来

此时我们看到用到了pthread库

因此我们可以看出,语言级别的线程库底层使用的是原生线程库。

Linux线程互斥

临界资源:多线程执行流共享的资源就叫做临界资源。

临界区:每个线程内部,访问临界资源的代码,就叫做临界区。

互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用

原子性(后面讨论如何实现):会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

观察下面现象:

如果多线程访问同一个全局变量,并对它进行数据计算,多线程会互相影响吗?

我们写了一个多线程抢票程序,这里tickets就是临界资源

我们看到打印了俩次0,就说明多卖了票。

这个抢票为什么会出错?

这是因为CPU执行--操作时,该线程可能会被其它线程挤下去,然后该线程带走自己的上下文数据,其它线程带着自己的上下文数据对tickets进行修改,修改后第一个线程恢复上来,tickets又变了。因此,tickets在并发访问的时候,导致了我们数据不一致的问题。

当进行数据运算时:1.读取数据到cpu内的寄存器中,2.cpu内部进行计算--3.将结果写回内存中

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

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

相关文章

基于Java+SpringBoot+vue+node.js的智能农场管理系统详细设计和实现

基于JavaSpringBootvuenode.js的智能农场管理系统详细设计和实现 博主介绍:5年java开发经验,专注Java开发、定制、远程、指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码…

报表设计器Stimulsoft 2023.2提供深色主题和 Monoline 图标包

Stimulsoft Reports 是一款报告编写器,主要用于在桌面和Web上从头开始创建任何复杂的报告。可以在大多数平台上轻松实现部署,如ASP.NET, WinForms, .NET Core, JavaScript, WPF, Angular, Blazor, PHP, Java等,在你的应用程序中嵌入报告设计器…

【Vue】学习笔记-消息的订阅与发布

消息的订阅与发布(基本不用) 消息订阅与发布(pubsub)消息订阅与发布是一种组件间的通信的方式,适用于任意组件间通信 消息订阅与发布 1.订阅消息∶消息名 2.发布消息︰消息内容 消息订阅与发布的工作流程: (A是订阅者,B是发布…

软件安全开发意识

国务院印发的《“十四五”数字经济发展规划》中指出数字经济是继农业经济、工业经济之后的主要经济形态,是以数据资源为关键要素,以现代信息网络为主要载体,以信息通信技术融合应用、全要素数字化转型为重要推动力,促进公平与效率…

MYSQL高可用配置(MHA)

1、什么是MHA MHA(Master High Availability)是一套优秀的MySQL高可用环境下故障切换和主从复制的软件。 MHA 的出现就是解决MySQL 单点的问题。 MySQL故障切换过程中,MHA能做到0-30秒内自动完成故障切换操作。 MHA能在故障切换的过程中最大…

fastapi高性能异步框架,极速上手

fastapi项目开发快速上手 简介 FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框架,使用 Python 3.6 并基于标准的 Python 类型提示。 特性: 快速:可与 NodeJS 和 Go 比肩的极高性能(归功于 Starlette 和…

全流程各工程类型地下水环境影响评价【一级】方法与MODFLOW Flex建模

目录 专题一 地下水基础知识学习 专题二 地下水环境影响评价过程讲解 专题三 地下水数值软件的学习及操作 专题四 相关专业软件(Surfer、Aquifer test)的学习 专题五 化工类建设项目地下水环评关键技术处理及上机操作 专题六 化工类建设项目地下水…

顶象助力如祺出行打造高品质服务

近日,广东省自然资源厅审批通过了如祺出行提交的测绘资质申请,如祺出行获得地理信息系统工程和互联网地图服务两个专业的乙级测绘资质。此次获批意味着,如祺出行能够在许可区域内依法合规开展数据标注和场景仿真等相关业务,构建全…

Oracle11g全新讲解之触发器和视图索引

触发器 1.触发器的基本讲解 当特定事件出现时自动执行的存储过程 语法结构 CREATE [OR REPLACE] TRIGGER trigger_name AFTER | BEFORE | INSTEAD OF [INSERT] [[OR] UPDATE [OF column_list]] [[OR] DELETE] ON table_or_view_name [REFERENCING {OLD [AS] old / NEW [AS]…

探索Jetpack Compose的Material You主题

探索Jetpack Compose的Material You主题 在本文中,我们将了解可用于 Jetpack Compose 的新 Material You 库,并了解我们如何将其动态颜色应用到 Stream Chat Compose SDK,以获得有趣、多彩和个性化的消息传递体验。 在本文中,您…

Spark大数据处理讲课笔记4.7 Spark SQL内置函数

文章目录 零、本讲学习目标一、Spark SQL内置函数(一)内置函数概述1、10类内置函数2、两种使用方式 (二)内置函数演示1、通过编程方式使用内置函数upper()2、通过SQL语句的方式使用内置函数upper()3、演示其它内置函数的使用 二、…

MySQL的安装和卸载-Linux版

MySQL8.0.26-Linux版安装 1. 准备一台Linux服务器 云服务器或者虚拟机都可以,Linux的版本为 CentOS7; 2. 下载Linux版MySQL安装包 https://download.csdn.net/download/weixin_44373940/87784825 3. 创建目录,并解压到对应目录中 mkdir mysql 解压到mysql目录中 tar -xvf…

【Arduino疑难杂症】:报错:上传失败:上传错误:exit status Oxffffffff

项目场景: 制作arduino宠物监控系统项目的过程中,摄像头方面使用到了ESP32Cam,制作过程中遇到了如下问题。 问题描述 [ERRORJ: (annot configure port, something wert wrong. 0riginal message: WindowsError(31,"xclxacxb…

Spring笔记-教程-快速回忆

title: Spring笔记 date: 2023-05-12 00:12:55 categories: 后端Java tags:JavaSpring Spring官网https://spring.io 框架图: 为什么要使用Spring 原先代码中存在的问题如下: 业务层: public class BookServiceImpl implements BookServi…

了解Swarm 集群管理

Swarm 集群管理 简介 Docker Swarm 是 Docker 的集群管理工具。它将 Docker 主机池转变为单个虚拟 Docker 主机。 Docker Swarm 提供了标准的 Docker API,所有任何已经与 Docker 守护程序通信的工具都可以使用 Swarm 轻松地扩展到多个主机。 支持的工具包括但不限…

SpringCache缓存常见问题

SpringCache 解决缓存常见问题 1 缓存穿透2 缓存雪崩3 缓存击穿 1 缓存穿透 缓存穿透是指缓存和数据库中都没有数据,而用户不断发起请求则这些请求会穿过缓存直接访问数据库,如发起为id为“-1”的数据或id为特别大不存在的数据。假如有恶意攻击&#xf…

TCP 和 UDP 协议详解

文章目录 1 概述2 TCP 协议2.1 报文格式2.2 三次握手,建立连接2.3 四次挥手,断开连接2.4 窗口机制 3 UDP 协议3.1 传输头格式 4 扩展4.1 常用端口号4.2 TCP 与 UDP 区别 1 概述 #mermaid-svg-aC8G8xwQRSdze7eM {font-family:"trebuchet ms",ve…

Mysql MHA高可用集群及故障切换

文章目录 一、MHA概述1.MHA的特点2. MHA的工作原理3.故障切换时MHA会做什么 二、实验搭建MySQLMHA1.配置主从分离2.安装MHA软件 总结 一、MHA概述 MHA(MasterHigh Availability)是一套优秀的mysql高可用环境下故障切换和主从复制的软件。 MHA解决了mysq…

铁路铁鞋UWB定位系统

在铁路运输过程中,当列车到达车站时,需要用专用铁鞋将列车固定在前轮和后轮上,以防止列车打滑和前进。所以,实时掌握铁鞋的位置信息十分重要,如果工人忘记撤回铁鞋子,则可能导致车辆停车和跳轨等事故频发。…

淘宝关键词搜索分析商品价格走势(商品列表接口,销量接口,价格接口,分类ID精准商品数据接口)接口代码对接

淘宝 OpenAPI(Open application programming interface)是一套 REST 方式的开放应用程序编程接口。淘宝网根据自己提供的电子商务基础服务,抽象并做成一系列的 API 接口。通过这些接口,可以让外部用户能够通过程序的方式访问淘宝网…