【Linux系统编程二十六】:线程控制与线程特性(Linux中线程库/线程创建/线程退出/线程等待)

news2024/11/15 10:45:45

【Linux系统编程二十六】:线程控制与线程特性

  • 一.Linux线程库pthread
    • 1.线程控制块
    • 2.线程tid
    • 3.线程栈
  • 二.线程控制
    • 1.线程创建
    • 2.线程退出
    • 3.线程等待
  • 三.线程的特性
    • 1.独立栈
    • 2.局部存储
    • 3.线程可分离

一.Linux线程库pthread

在Linux中,是没有明确的线程概念的,因为在内核里线程是用进程的内核数据结构模拟实现的,只有轻量级进程的概念。所以在内核里它没有直接控制线程的接口,只有通过轻量级进程的系统调用:
在这里插入图片描述

简单介绍一下该系统调用,第一个参数就是一个函数指针,是该线程要执行的方法函数。第二个参数是一个栈指针,指向的是一块栈空间。至于为什么要栈,后面会讲到。

而我们用户只想要线程,不想要所谓的轻量级进程,所以就有人在应用层给我封装了一个线程库pthread。
它内部就是封装了调用轻量级进程系统调用接口,然后给我封装出一批创建线程,退出线程,等待线程的接口。
所以我们想使用Linux中的线程,需要使用第三方pthread库。
在这里插入图片描述

1.线程控制块

我们要理解的是,操作系统内部是没有线程的,只有轻量级进程,线程是在用户层创建出来的。是用户层维护线程的,所以叫做用户级线程。

因为库是共享的,你在库中可以创建线程,我也可以创建线程,所以线程库中会存在很多的线程,这些线程需不需要管理呢?当然需要!
所以线程库需要维护线程,要管理线程—先描述,再组织。
在线程库中其实是存在一个叫做线程控制块的结构体,它就是用来描述线程的各种属性比如线程的字段,线程的独立栈,线程的回调函数等。
在这里插入图片描述

创建一个线程,线程库就需要开辟一块空间,用来创建线程的TCB。
这是在用户层面,而线程库使用时,是需要加载到内存面的,在内核层面,没有所谓的TCB(线程控制块),它只有一个执行流,也就是轻量级进程,对于操作系统来说,它管理的是执行流。每一个线程在内核里都有唯一的LWP。

2.线程tid

根据上面的理解,我们知道在用户层面,线程库会为线程创建线程控制块TCB。那么这么多线程,用户是如何区别这些线程的呢?每一个线程在用户层都会存在一个tid,用来区别不同线程的。根据动态库的学习,我们知道,在链接时需要将动态库加载到内存的共享区里。
在这里插入图片描述
而线程库链接时也需要加载到内存的共享区里。
线程库里存在很多描述线程的线程控制块TCB。线程库按照数组的方式将各个线程控制块管理起来。所有的线程都在动态库里被管理着。
而这些线程控制块在共享区里的起始地址就就是该线程的tid。
因为为了更好的找到每个TCB在共享库里的位置,每一个TCB在内存的起始地址就称为线程的tid
在这里插入图片描述
在这里插入图片描述

所以线程的tid本质就是共享区内存的一个虚拟地址。

线程获取自己的tid,系统调用接口:
在这里插入图片描述

3.线程栈

线程控制块里还存在一个线程栈,这是什么,又为什么呢?
因为每一个线程都是一个独立的执行流,各自在不同的地址空间执行代码,也就是执行不同的函数。而函数里难免会需要各种临时变量,或者调用其他函数,传递形参等,这些都需要栈空间存储。所以每一个线程创建时,都需要一块栈空间,用来维护各自函数里临时变量。
而主线程用的是地址空间里的栈空间。其他线程用的都是共享区里开辟的空间。在线程创建时,首先在上层线程库会为新线程创建一堆数据结构叫做线程控制块,里面维护着该线程的tid,该线程的栈空间等。上层将tcb创建好后,在内核层面就会形成对应的执行流,然后调用系统调用clone,将线程的栈空间,传递给child_stack。
在这里插入图片描述
在这里插入图片描述

二.线程控制

1.线程创建

main函数执行流我们称为是主线程,主线程创建的线程我们称为新线程。
在这里插入图片描述

1.pthread_create()是用来创建线程的。它会产生一个线程id,用来标识该线程的唯一性。线程的后续操作,都是根据该线程的id来操作线程。该id的本质就是共享区内存的虚拟地址。(就是线程控制块TCB在共享区内存的起始地址)
2.pthread_t是操作系统提供的数据类型,类似于int
3.第一个参数是用作输出型参数的,它是将创建的线程的tid给带出来。
4.第二个参数,是用来控制线程的属性,不用的情况下,置nullptr就可以。
5.第三个参数是一个函数指针,它是该线程要执行的方法函数。该函数的返回值和参数都是void类型。
6.第四个参数,是新线程执行的方法函数的参数。执行线程函数也可能需要参数,所以这个参数,就是主线程传给新线程的参数。主线程想让子线程知道什么,就可以通过该参数传递给子线程。该参数是一个void
类型,不过注意不仅可以传普通类型过去还可以传对象过去。
7.
注意在编译的时候需要链接线程库。
在这里插入图片描述

创建的线程和主线程是如何区分的呢?
在这里插入图片描述
根据LWP来区别,或者根据它们的tid来区别。
任何一个线程被干掉,进程就会被干掉,因为线程就是进程内部的一个执行分支。

2.线程退出

线程执行的函数返回了,就代表线程退出。
线程退出有两种方式,一种是函数之间return返回退出。
一种是使用系统调用pthread_exit()来退出。
注意不能使用exit()接口,它是用来进程退出的:
在这里插入图片描述

注意线程退出,返回的是void*类型,如果没有什么可以退出的,就直接返回nullptr;
形参可以接收一个对象指针,那么最后函数返回时也可以返回一个对象。
在这里插入图片描述

3.线程等待

主线程创建新线程,也需要观看这个新线程,需要等待新线程。不然会出现类似于"僵尸进程"。导致资源泄露。并且有时主线程也需要获取创建的线程的一些处理结果。所以需要主线程最后退出。

在这里插入图片描述

1.pthread_join()是用来进程等待的。
2.第一个参数就是线程的tid,创建线程时,会将线程的tid给带出来。
3.第二个参数retval是一个输出型参数,是用来将线程执行函数结果给带出来,因为线程执行的函数的返回值是void类型,要想将该类型带出来,必须使用指针类型,也就是void *类型。
在这里插入图片描述4.主线程在等待时,默认是阻塞等待,如果线程不退出,那么主线程就会一直等待。
5.线程退出时,主线程join等待不需要考虑异常情况,因为做不到,只有进程才可以做到。
6.一般来说,主线程退出,其他线程也会退出,但是如果使用pthread_exit(主线程tid),其他线程可能不会退出。

主线程还可以取消新线程(该线程已经被创建完毕)
在这里插入图片描述
线程如果被取消,那么它执行的函数返回值就会为-1。

三.线程的特性

因为在内核里没有线程的具体实现,线程是在用户层利用第三方库实现的,所以在内核中是如何区分线程和进程的呢?在内核中是没有线程和进程概念,它们都是轻量级进程,而每一个轻量级进程都有唯一标识它的LWP(Light Weight Process)。
而与之对应的用户层是真正实现了线程,所以叫做用户级线程,每一个线程都具有自己的tid。
所以这样对应起来,真正的线程是具有两部分构成的,一个是上层用户级线程,一个是下层内核级线程。用户级执行流:内核LWP=1:1

1.独立栈

每一个线程都是具有独立的栈结构的,当不同的线程调用同一个函数时,虽然是同一个函数,但是在不同的函数栈帧中。所以同一个变量,是在不同的栈帧中开辟的,地址也就不会相同。在这里插入图片描述

但这个独立是相对独立,不是真的独立私有,其他线程想要访问你的栈也是可以访问到的,因为这些线程都是在同一个地址空间上运行,所以在线程中是没有秘密可言的,只不过线程之间最好不要越界访问。
在这里插入图片描述

2.局部存储

全局变量是每一个线程都可以访问的,就相当于一个共享资源。访问到的都是同一个。
在这里插入图片描述
那么有没有这样的变量,它是线程独有的,但又是全局的呢?也就是线程想要一个私有的全局变量呢?

线程的局部存储可以满足这样的条件。什么叫局部存储呢?
在这里插入图片描述
在线程控制块里,存储着一个叫线程局部存储的对象。它也是一块空间,是在共享区内存提前申请好的,当线程创建时,就已经存在。也就是每一个线程都会有一个局部存储,它可以用来定义全局变量,但这个全局变量又只有该线程能访问。
可以将线程自己不变的属性,且频繁调用的,放进去。然后再交给线程。

可能有人会疑惑,为什么要有局部存储,为什么不在线程函数内部直接设置一个临时变量不就好啦。
这样做的原因很简单,效率高,不需要频繁的创建变量,不需要传递参数也可以使用。

3.线程可分离

主线程创建新线程后,有两种情况,一种是等待新线程,一种是不等待线程。
等待线程,可能需要获取新线程的返回结果。
不等待线程,说明不关心新线程的返回结果。
在这里插入图片描述

但是你又说了,不等待线程会造成资源泄露。
有一种方法可以让主线程,不等待新线程,也不会造成资源泄露:线程分离

也就是主线程与新线程分离后,主线程不关心新线程的返回结果,新线程退出后会自动释放资源。

int pthread_detach(pthread_t thread);
//thread就是线程的tid

线程分离有两种方法:
①主线程强制分离
在主线程里,利用创建线程获取得到的tid,来分离新线程。
②新线程主动分离
在新线程执行的函数里,利用(pthread_self()获取自己的tid来分离。

在这里插入图片描述

【注意】
①一个线程被分离后,主线程就不能再等待它了。如果该线程分离后,主线程又等待它,这样会报错。
②分离线程,说明主线程是不关心该线程的返回结果,也就是不想等待该线程,但还是要保证主线程,最后退出。
在这里插入图片描述

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

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

相关文章

Sonarqube安装(Docker)

一,拉取相关镜像并运行 # 拉取sonarqube镜像 docker pull sonarqube:9.1.0-community在运行之前要提前安装postgres并允许,新建数据库名为sonar的数据库 Docker安装postgres教程 docker run -d --name sonarqube --restartalways \ -p 19000:9000 \ …

Redis 持久化—RDB

文章目录 1. 为什么需要Redis持久化?2. Redis持久化的几种方式3. RDB简介4. 持久化触发4.1 手动触发4.1.1 save命令4.1.2 bgsave 命令 4.2 自动触发4.2.1 save m n4.2.2 flushall4.2.3 主从同步触发 5. 配置说明6. 配置配置7. 配置配置8. RDB 文件恢复9. RDB 优缺点…

ResNet论文阅读和简单实现

论文:https://arxiv.org/pdf/1512.03385.pdf Deep Residual Learning for Image Recognition 本模块主要是阅读论文,会做简单的翻译(至少满足我自己能看明白)。 Introduction 由上图可见,在20层和56层的网络上训练的…

深信服技术认证“SCCA-C”划重点:云计算关键技术

为帮助大家更加系统化地学习云计算知识,高效通过云计算工程师认证,深信服特推出“SCCA-C认证备考秘笈”,共十期内容。“考试重点”内容框架,帮助大家快速get重点知识。 划重点来啦 *点击图片放大展示 深信服云计算认证&#xff08…

滑动窗口最大值(力扣239题)

单调递减队列: 在解决题目之前,我们先来了解一下单调递减队列,它其实就是在队列的基础上多加了一些限制,如下图: 要求队列中的元素必须按从大到小的顺序排列。 如果向单调递减队列中加入数字 1,可以直接加入…

基于ElementUI封装的下拉树选择可搜索单选多选清空功能

效果&#xff1a; 组件代码 /*** 树形下拉选择组件&#xff0c;下拉框展示树形结构&#xff0c;提供选择某节点功能&#xff0c;方便其他模块调用* author wy* date 2024-01-03 * 调用示例&#xff1a;* <tree-select * :height"400" // 下拉框中树形高度* …

智慧工厂:科技与制造融合创新之路

随着科技的迅猛发展&#xff0c;智慧工厂成为制造业领域的热门话题。智慧工厂利用先进的技术和智能化系统&#xff0c;以提高生产效率、降低成本、增强产品质量和灵活性为目标&#xff0c;正在引领着未来制造业的发展。 智慧工厂的核心是数字化和自动化生产&#xff0c;相较于传…

C语言实例_string.h库函数功能及其用法详解

一、前言 在计算机编程中&#xff0c;字符串处理是一项常见而重要的任务。C语言的string.h头文件提供了一系列函数和工具&#xff0c;用于对字符串进行操作和处理。这些函数包括字符串复制、连接、比较、查找等功能&#xff0c;为开发人员提供了强大的字符串处理能力。本文将对…

vue3中pdf打印问题处理

1 get请求参数问题 之前的请求是post得不到参数&#xff0c;今天发现的问题很奇怪&#xff0c;从前端进入网关&#xff0c;网关居然得不到参数。 前端代码 const print () > {let linkUrlStr proxy.$tool.getUrlStr(proxy.$api.invOrder.psiInvOrder.printSalOutstock,{a…

Winform中使用Websocket4Net实现Websocket客户端并定时存储接收数据到SQLite中

场景 SpringBootVue整合WebSocket实现前后端消息推送&#xff1a; SpringBootVue整合WebSocket实现前后端消息推送_websocket vue3.0 springboot 往客户端推送-CSDN博客 上面实现ws推送数据流程后&#xff0c;需要在windows上使用ws客户端定时记录收到的数据到文件中&#x…

鸿蒙开发学习——基本组件

文章目录 引言正文Image组件设置加载网络图片图片属性设置 Text组件设置文本显示内容text属性设置 TextInput输入文本TextInput Controller获取输入文本 Button按钮 引言 最近在学习鸿蒙系统开发&#xff0c;然后对着文档看还是有很多问题&#xff0c;这里结合官方给的demo进行…

在ARMv8中aarch64与aarch32切换

需求描述 在项目调试过程中,由于内存或磁盘空间不足需要将系统从aarch64切换到aarch32的运行状态去执行,接下来记录cortexA53的调试过程。 相关寄存器描述 ARM64: SPSR_EL3 N (Negative):表示运算结果的最高位,用于指示运算结果是否为负数。 Z (Zero):表示运算结果是否…

设计模式之建造者模式【创造者模式】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档> 学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某…

c++语言基础17-判断集合成员

题目描述 请你编写一个程序&#xff0c;判断给定的整数 n 是否存在于给定的集合中。 输入描述 有多组测试数据&#xff0c;第一行有一个整数 k&#xff0c;代表有 k 组测试数据。 每组数据第一行首先是一个正整数 m&#xff0c;表示集合中元素的数量&#xff08;1 < m &…

程序媛的mac修炼手册--MacOS系统更新升级史

啊&#xff0c;我这个口罩三年从未感染过新冠的天选免疫王&#xff0c;却被支原体击倒&#x1f637;大意了&#xff0c;前几天去医院体检&#xff0c;刚检查完出医院就摘口罩了&#x1f926;大伙儿还是要注意戴口罩&#xff0c;保重身体啊&#xff01;身体欠恙&#xff0c;就闲…

微软截图工具SnippingTool_6.1.7601免费版

SnippingTool是一款win7系统自带的一款非常实用型截图工具&#xff0c;操作简单&#xff0c;点击“新建"可一键截图&#xff0c;截图之后会弹出编辑器&#xff0c;可以进行一些简单的勾画编辑操作&#xff0c;您可以使用笔、荧光笔、电子邮件或保存等选项。如果您的系统丢…

Liunx(CentOS)安装Nacos(单机启动,绑定Mysql)

Liunx安装Nacos(单机启动&#xff0c;绑定Mysql) 一&#xff0c;准备安装包 github下载点 二&#xff0c;在/usr/local/目录下创建一个文件夹用于上传和解压Nacos cd /usr/local/ #这里创建文件夹名字可随意&#xff0c;解压后会生成一个名为nacos的文件夹&#xff0c;后续…

全国计算机等级考试| 二级Python | 真题及解析(6)

全国计算机等级考试二级Python真题及解析(8)图文 一、选择题 1.python中表达式4**3=( )。 A.12 B.1 C.64 D.7 2.在Python中,通过( )函数查看字符的编码。 …

安全防御之授权和访问控制技术

授权和访问控制技术是安全防御中的重要组成部分&#xff0c;主要用于管理和限制对系统资源&#xff08;如数据、应用程序等&#xff09;的访问。授权控制用户可访问和操作的系统资源&#xff0c;而访问控制技术则负责在授权的基础上&#xff0c;确保只有经过授权的用户才能访问…

jQuery框架

1.1、jQuery简介 jQuery 是一个高效、精简并且功能丰富的 JavaScript 工具库。它提供的 API 易于使用且兼容众多浏览器&#xff0c;这让诸如 HTML 文档遍历和操作、事件处理、动画和 Ajax 操作更加简单。目前超过90%的网站都使用了jQuery库&#xff0c;jQuery的宗旨&#xff1…