线程(下)【Linux操作系统】

文章目录

  • 线程控制
    • 线程共享进程地址空间中的所有数据
    • 线程会瓜分进程的时间片
    • 线程相关库函数
      • 库函数:pthread_create
      • 库函数:pthread_self
      • 库函数:pthread_join
      • 库函数:pthread_exit
      • 库函数:pthread_cancel[尽量少用]
      • 库函数:pthread_detach
    • 为什么Linux线程相关的函数是库函数,而不是系统调用?
    • 线程的状态和回收
    • 线程终止
    • 线程分离
  • 线程库的理解
    • 线程id
    • 线程TCB的存放和内容
      • TCB的存储
      • TCB中的内容
    • 线程的栈
    • 线程局部存储

线程控制

线程共享进程地址空间中的所有数据

全局区,代码区,堆区,共享区,栈区都是共享的
没错栈区也是共享的
只要线程a能拿到线程b函数中的栈区变量的地址,就可以对它进行修改和访问
而且修改之后,线程b看到的也是改变了之后的【但是强烈不建议这么做



线程会瓜分进程的时间片

即:
一个进程的时间片是10ns
它一共有5个线程,那么每个线程的时间片就是2ns

为什么呢?
操作系统进行调度时,是以线程为单位的
而且承担资源分配的基本实体是进程,进程是资源的容器,线程瓜分进程资源,而时间片也是资源!!!
所以,必须给每个线程分时间片,这样操作系统调度线程时,才知道线程什么时候时间片耗尽

而且:
如果线程的时间片和它所属的进程一样长,那么一个进程就可以通过创建更多线程,来延长自己占用CPU的时间
这显然对其他进程不公平
这和分时操作系统的公平调度理念不符



线程相关库函数

库函数:pthread_create

  • 头文件:pthread.h

  • 返回值:int
    ①成功,就返回0
    ②失败,就返回错误码

  • 参数表:

    • pthread_t *thread:输出型参数,用于获取线程tid,本质也是一个整型

    • const pthread_attr_t *attr:线程属性

    • (void*)(*p)(void*):指向给这个线程分配的函数的函数指针

    • void*arg:传给分配给线程的函数的参数
      为什么是void * 类型?
      就是为了支持接收任意类型的参数,变量,数组,对象都可以传递过去
      这样就可以一次传递多个信息,也可以更好地支持C和C++混合编写
      我们可以定义一个任务类,里面存储一些基本信息和分配给这个新线程的任务函数
      即设计一个任务类,里面的成员变量就是任务函数,以及任务函数对应的参

  • 作用:创建一个线程


库函数:pthread_self

  • 头文件:pthread.h

  • 返回值:pthread_t
    ①成功,该线程的tid
    ②失败,就返回-1

  • 作用:获取唯一标识一个线程的tid


库函数:pthread_join

  • 头文件:pthread.h

  • 返回值:int
    ①成功,0
    ②失败,就返回错误码

  • 参数表:

    • pthread_t thread:要等待的线程的tid

    • void**retval因为分配给新线程的函数的返回值为void*类型的,所以以输出型参数的方式获取这个返回值就得是void**类型的

  • 注意:
    pthread_join是阻塞等待的从线程的
    一般是主线程等待从线程


库函数:pthread_exit

  • 头文件:pthread.h

  • 返回值:void

  • 参数表:
    void*retval:线程的退出信息

  • 作用:让调用这个函数的线程退出


库函数:pthread_cancel[尽量少用]

  • 头文件:pthread.h

  • 返回值:int
    ①成功,0
    ②失败,就返回错误码

  • 参数表:
    pthread_t thread:要取消的线程的tid

  • 注意:

    • ①一个线程能被取消的前提是,这个线程已经启动了
    • ②被取消的线程也必须被回收(不然会内存泄露),而且退出码必定是-1

库函数:pthread_detach

  • 头文件:pthread.h

  • 参数表:
    pthread_t thread:要设置成要把被等待状态设置成detach的线程的tid

  • 作用:
    把指定线程的被等待状态设置成detach



为什么Linux线程相关的函数是库函数,而不是系统调用?

因为在Linux中没有给线程先描述再组织
Linux内核中的线程是使用LWP模拟实现的
所以
Linux内核就只给我们提供了创建LWP的系统调用接口(即系统调用clone)操作线程的各种系统调用通通没有

用户不愿意直接去使用创建LWP的系统调用,因为使用这个系统调用还得去了解Linux的LWP等相关知识,成本太高了
所以
Linux就让库封装了LWP相关的系统调用得到了给用户提供了包括线程的各种操作的的库[库的名字就叫pthread]
封装成库之后,用户就只需要知道线程的相关概念就可以轻松使用库函数了

所以
Linux中,如果使用了线程相关的函数,那么编译时都要链接pthread动态库,pthread是Linux实现的自带的库,是原生线程库
所以:
其他任何语言想要在Linux平台上支持线程,就必须封装Linux的pthread库



线程的状态和回收

Linux中的线程就是用LWP(task_struct)模拟实现的
所以一个线程对应一个PCB
而PCB里面就存储了状态信息
所以线程的状态管理,完全可以复用进程的状态管理

从线程也需要被主线程等待和回收(使用pthread_join)
为什么❓

  • ①因为线程的退出信息,在线程库中它自己的TCB中维护着,而TCB关联内核的LWP的生命周期

  • ②因为主线程也要知道其他线程把任务执行地怎么样
    所以如果不等待,即使线程退出了,操作系统也不敢释放它的LWP,就会出现与进程类似的僵尸问题



线程终止

会让线程终止的方法一共3种

  • ①分配给线程的函数return,该线程会退出

  • ②线程调用了pthread_exit,该线程就会退出

  • ③其他线程(一般是主线程)调用pthread_cancel取消线程



线程分离

主线程也可以不等待新线程,自己干自己的事
但是线程并不提供非阻塞等待

而是通过修改新线程的被等待状态,来做到

线程有两种被等待的状态

  • joinable:线程需要被join等待和回收(新线程默认是joined状态

  • detach:线程分离(这种状态的线程执行完了就直接退出,即使主线程调用pthread_join专门去等待分离状态的线程,也会因为检测到它是分离状态,主线程直接返回,不会阻塞

此时要注意一点:
如果线程a和主线程分离了,如果线程a还在运行,主线程就退出了
这个时候可能:[不同的线程库实现不一样]

  • ①因为主线程退出,进程就直接也退出了
  • ②主线程退出,但是进程没退出

注意:
多执行流时,尽可能保证主执行流最后退出=



线程库的理解

线程id

我们使用的pthread库里面的接口的时候,使用的事线程id来操纵线程

但是线程id并不是Linux内核中的LWP,而是线程库pthread自己维护的

线程id的本质是地址,是线程库维护的这个线程对应的结构体(tcb)变量的起始地址

创建线程的时候,不仅Linux内核中会创建一个LWP描述线程的内核级属性,线程库pthread中也会创建一个TCB结构体变量来维护线程的用户级属性
即线程库中,会对线程进行先描述,再组织

为什么❓

  • ①LWP是描述轻量级进程的,但是用户使用的是线程,所以用户需要的是线程的属性,而不是轻量级进程的属性
    LWP并不能非常好地描述用户所需的线程的属性(比如:LWP中没有维护这个线程的栈大小,而线程库TCB中维护了)

  • ②为了解藕
    因为用户使用的是线程库,线程库创建出来的线程是用户级线程
    用户级线程不宜和内核级线程共用结构体和数据结构,这样耦合度太高,而且还要使用系统调用陷入内核


线程库哪里来的空间对线程先描述再组织?
线程库维护进程的线程使用的空间是进程自己申请的物理内存(即使用进程的共享区内存来存储

因为这样描述线程属性的结构体变量才有虚拟地址,执行流执行线程的库函数的时候,才能找到并访问到对应的数据

所以就可以做到:
不同的进程,使用同一个线程库,但线程库中维护的线程是不一样的
但是线程库的代码区的代码是一样的



线程TCB的存放和内容

TCB的存储

线程库中TCB的存储是在共享区中开辟一块内存,把所有TCB以及对应的连续存储在一起的

在这里插入图片描述

TCB中的内容

  • ①对应的轻量级进程的LWP
  • ②对应进程的pid
  • ③void*result:分配给自己这个线程的函数的返回值
  • ④用户分配给自己这个线程的入口函数的地址,以及它对应的参数
  • ⑤自己这个线程对应的栈的起始虚拟地址,以及栈的大小
  • ⑥线程对应的线程局部存储的起始地址,及其大小
  • ⑦标记线程是否分离的bool类型的变量


线程的栈

线程的栈是独立的,分两种

  • ①进程地址空间中所谓的栈区,其实是专门给主线程用的栈区,大小是不固定的,可以扩容(当然也受到mm_struct的区域划分限制

  • ②新线程的栈区是在创建新线程的同时,使用mmap在共享区开辟的一块的大小固定(一般默认是8MB,如果用满了8MB还用就会栈溢出)的内存

所以才说线程的栈是独立的



线程局部存储

一个全局变量b本来是被所有线程共享的,所有线程访问这个的全局变量b时,访问的虚拟地址是同一个

如果定义时,给全局变量b前面加一个__thread那么这个全局变量b就不是被所有线程共享的了,不同线程访问这个变量b时,访问的虚拟地址不同

这是怎么做到的?
如果一个变量a定义时加了__thread,编译器编译的时候,如果创建了新线程,就会在这个线程的线程局部存储中开辟空间存储一份这个变量a
以后这个线程访问这个变量a的时候,就只访问自己线程局部存储中的变量a

注意:
__thread只能修饰内置类型的变量

线程局部存储的作用:

  • ①不同线程对应一个独立的错误码,比如C标准库提供的errno

  • ②缓存线程属性等访问频繁的数据,提高访问速度

比如:
可以加__thread定义一个全局的线程id,在对应线程里面初始化之后
这样以后使用线程id,就不需要在调用pthread_self,直接用它,不同的线程看到的也是自己的id

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

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

相关文章

Linux 任务调度策略

🌟 概述 Linux 内核以线程(任务)为单位进行调度,支持 SCHED_FIFO 和 SCHED_RR(实时调度)以及 SCHED_OTHER(基于 CFS,非实时调度)。 🔍 调度策略 1. SCHED_…

芯片金属层M1、M2区别

在芯片设计中,M1(第一层金属)和 M2(第二层金属)是常见的金属层,它们在用途、布线方向、设计规则和应用场景等方面存在一些主要区别。以下是详细对比: 1. 用途 M1(第一层金属&#x…

Linux离线环境下安装Lean 4开发环境的完整指南

文章目录 一、准备工作1. 在线环境下载必要文件2. 传输文件至离线环境 二、安装elan工具链管理器1. 解压并安装elan2. 配置环境变量3. 验证elan安装 三、安装Lean 4二进制包1. 解压Lean 4二进制文件2. 注册工具链到elan 四、安装VS Code Lean 4插件1. 使用VS Code界面安装插件 …

ffmpeg windows 32位编译

ffmpeg windows 32位编译 编译后程序下载 编译方式 自动编译工具套件 – https://github.com/m-ab-s/media-autobuild_suite github克隆完成后,双击bat文件打开编译窗口,注意git检出的目录需要简短,最好选一个盘的根目录。 选择编译版本…

P1216 [IOI 1994] 数字三角形 Number Triangles

题目描述 观察下面的数字金字塔。 写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。 在上面的样例中,从 7 → 3 → 8 → 7 → 5 7 \to 3 \to 8 \to 7 \to 5 7→3→8→7→5 的…

(二)原型模式

原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…

Css实现悬浮对角线边框动效

动画效果展示 鼠标悬停时,一个带有圆角的水绿色边框会从右上和左下两个方向快速展开,随后颜色缓慢填充;移出鼠标时颜色先褪去,边框再快速收缩消失,形成具有节奏感的呼吸式动画。 📜 动画原理说明 一、核…

技术创新究竟包含什么?

技术创新指的是引入新技术或改进现有技术,以创造新颖且更优的产品、服务或流程的过程。它涉及应用科学和技术知识开发创新解决方案,以创造价值、提高效率、推动增长,并满足用户和客户不断变化的需求。 技术创新可以有多种形式,例…

ArcGIS+AI:涵盖AI大模型应用、ArcGIS功能详解、Prompt技巧、AI助力的数据处理、空间分析、遥感分析、二次开发及综合应用等

🌐 GIS凭借其强大的空间数据处理能力、先进的空间分析工具、灵活的地图制作与可视化功能,以及广泛的扩展性和定制性,已成为地理信息科学的核心工具。它在城市规划、环境科学、交通管理等多个学科领域发挥着至关重要的作用。与此同时&#xff…

数据淘金时代:公开爬取如何避开法律雷区?

首席数据官高鹏律师团队编著 一、“数字淘金热”里的暗礁:那些被爬垮的平台和赔哭的公司 前阵子某电商平台的“商品比价爬虫”上了热搜,技术小哥本想靠抓竞品数据优化定价,结果收到法院传票——对方服务器被爬瘫痪,索赔300万。这…

在ARM 架构的 Mac 上 更新Navicat到17后连接Oracle时报错:未加载 Oracle 库。

一:问题 使用的M1芯片的Mac,将Navicat更新到了17版本后,原本正常的Oracle数据库无法连接,报错:未加载 Oracle 库。而sqlserver库可以正常连接 二:解决方法 打开聚焦搜索——〉打开访达——〉在应用程序中…

Springboot仿抖音app开发之用短视频务模块后端复盘及相关业务知识总结

Springboot仿抖音app开发之用户业务模块后端复盘及相关业务知识总结 BO类和VO类的区别 BO (Business Object) - 业务对象 定义: 业务对象是包含业务逻辑的领域模型用途: 主要用于封装业务逻辑相关的数据,在业务层(Service层)之间传递特点: 与业务处理密切相关通常…

SQL-事务(2025.6.6-2025.6.7学习篇)

1、简介 事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。 默认MySQL的事务是自动提交的,也就是说&#xff0…

《Ansys SIPI仿真技术笔记》 E-desk IBIS模型导入

技术笔记日期:20250611 00 背景和疑问 当在Circuit中准备载入IBIS时,工作界面会弹出如下界面: 那么具体Pin Import和Buffer Import有和区别? 何时该按哪个导入呢? 01 思考和记录 1. Buffer Import VS Pin Import…

uniapp的请求封装,如何避免重复提交请求

1、如何封装uniapp,并且如何使用uniapp的封装查看👉uniapp请求封装_uni-app-x 请求封装-CSDN博客​​​​​​​ 2、声明一个请求记录的缓存,代码如下 // 存储请求记录 let requestRecords {}; // 重复请求拦截时间(毫秒&#x…

【云原生】阿里云SLS日志自定义字段标签实现日志告警

把业务日志接入到阿里云SLS日志服务后,我们想自定义字段做为标签,在做日志告警的时候,可以做为查询结果使用 自定义标签 样例: 一个典型的java log初始化日志格式 [ywgy-app-service:10.10.6.100:30000] 2025-06-10 08:40:53.444 INFO 1[TID: N/A][uId:][sId:][tId:][po…

Linux下制作Nginx绿色免安装包

linux下安装nginx比较繁琐,遇到内网部署环境更是麻烦。根据经验将nginx打包一个绿色版进行使用。 大体思路,在一台正常的机器上面制造好安装包,然后上传到内网服务器,解压使用 安装包制作 安装依赖 yum install gcc-c pcre per…

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)

一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…

【Zephyr 系列 18】分布式传感网络系统设计:从 BLE Mesh 到边缘网关的数据闭环

🧠关键词:Zephyr、BLE Mesh、边缘网关、分布式网络、状态同步、组播、数据聚合、远程控制 📌适合人群:希望实现 BLE Mesh 与网关联合控制、多设备组网协作、数据闭环采集的开发者 📊预计字数:5500+ 字 🧭 背景与系统目标 在工业、农业、仓储等场景中,我们常见以下…

【区块链基础】区块链的 Fork(分叉)深度解析:原理、类型、历史案例及共识机制的影响

区块链的 Fork(分叉)全面解析:原理、类型、历史案例及共识机制的影响 在区块链技术的发展过程中,Fork(分叉)现象是不可避免且极具影响力的一个环节。理解区块链分叉的形成原因、具体表现以及共识机制对分叉的作用,对于深入把握区块链技术架构及其治理机制至关重要。 本…