线程的概念和控制
文章目录
- 线程概念
- 线程的优点
- 线程的缺点
- 线程异常
- 线程用途
- 理解虚拟地址
- 线程控制
- 线程的创建
- 线程终止
- 线程等待
- 线程分离
- 封装线程库
线程概念
什么是线程?
- 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序
列 - 一切进程至少都有一个执行线程
- 线程在进程内部运行,本质是在进程地址空间内运行
- 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化,线程是比进程更加轻量化的一种执行流。
- 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程
执行流
如何看待之前的进程?
之前的进程是内部只有一个执行流。
如何看待现在的进程?
现在的进程内部有多个执行流。并且多个执行流共享大部分资源。
线程更像是一种标准,各个平台的实现方式可能不同,但是作用都是一样的。在Linux中,因为线程也是执行流,进程也是,并且一个进程内的所有线程共享大部分资源。所以Linux中线程的实现就直接复用了进程的代码,这样在OS的调度算法就只有一个进程调度就可以了,一个进程中的的线程是共享大部分数据,所以创建线程可以直接复制PCB就可以了,一个进程中是可以存在多个线程的,所以OS也一定会对线程进行管理,所以OS也一定要有对线程描述的结构体(TCB),但是线程是直接复制进程的,所以Linux中描述线程的结构体也是PCB。所以Linux下线程也称为轻量级进程。
因此现在看来,线程是CPU调度的基本单位,进程就是承担系统资源的基本实体。
线程的优点
- 创建一个新线程的代价要比创建一个新进程小得多
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
为什么说创建线程比进程的代价小呢呢?
因为线程是在进程的地址空间中运行的,并且线程创建更简单,只需要复制进程的PCB,只有一小部分的数据是私有的,大部分数据都和进程是一样的。
线程切换的效率为什么高?
如果是一个进程中的两个线程进程切换的话,CPU中的有一部分寄存器中的内容是不需要被切换的,并且因为局部性原理,CPU中是存在Cache缓存的,如果是一个进程中的两个线程进程切换,根据局部性原理Cache缓存也大部分不会被替换,但是如果是进程切换,所有的寄存器和Cache都是要被切换的。
线程的缺点
- 性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。 - 健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。 - 缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。 - 编程难度提高
编写与调试一个多线程程序比单线程程序困难得多
线程异常
- 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
- 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该
进程内的所有线程也就随即退出
线程用途
- 合理的使用多线程,能提高CPU密集型程序的执行效率
- 合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)
我们说线程和线程之间大部分数据是共享的但是有一部分数据是私有的,那么什么共享什么私有?
共享
文件描述符表
每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
当前工作目录
用户id和组id
地址空间
私有
线程ID(lwp)
一组寄存器
栈
errno
信号屏蔽字
调度优先级
理解虚拟地址
我们现在直到磁盘中文件是以4KB为单位存储的,称之为页帧。并且我们编译好的可执行程序仍然遵守这样的规则,所以我们的内存空间也是被划分为4KB大小为单位的空间,称之页框,所以在访问一块内存时只需要知道页框的首地址+页内偏移就可以访问内存中的任意一个地址空间。因为内存会被划分成很多的页框,所以OS要对内存管理,就需要先描述在组织,可以理解为所有的页框都被放在一个数组中,然后OS对内存的管理就变成了对数组的增删查改。
虚拟地址到物理地址的转换是需要页表的,页表的每一行存在很多的字段,假设现在是10个字节,要是每个物理地址都存在一个虚拟地址跟他直接映射的话,假设是2^32的内存,就需要40G来存放页表,显然是不可能的,所以虚拟地址和物理地址并不是直接进行映射的。
以32为的地址为例假设先现在有一个地址 11110011 10111011 00101001 10100101 一个32个比特位,把前10 为1111001110作为一个整体,一共10个比特位,可以表示的范围就是0~1023,所以假设有一个1024大小的数组,就可以通过前十位的数据找到一个数组的下标,数组的内容还是一个大小为1024的数组,这个数组为页目录,然后11 ~ 20为比特位1110110010作为数组指向的那个数组的下标,数组的内容就是页框的起始地址,然后最后12个比特位就是页内的偏移地址。所以通过这样的方式找到物理地址,并且大大的减少了直接映射的使用空间,因此在页表中是没有物理地址的,在CPU中有一个MMU寄存器,我们只需要把一个虚拟地址放进去,就可以值就拿到物理地址然后进行访问。当然CPU中也有一个寄存器专门保存的就是当前页目录的起始地址。
每个线程要执行自己的代码,根据我们传递的函数,本质就是划分页表,划分页表的本质就是划分地址空间。所以在进程的视角,虚拟地址空间本身就是资源。
进程和线程关系如下:
线程控制
Linux中是没有真正的线程的,只有轻量级进程的概念,所以OS只会提供轻量级进程的系统调用,不会直接提供线程调用的接口。所以为了便于人们对线程的控制,写Linux的程序员就把对线程的控制封装成了pthread原生线程库。对上提供线程控制的接口。
- 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
- 要使用这些函数库,要通过引入头文<pthread.h>
- 链接这些线程函数库时要使用编译器命令的“-lpthread”选项
线程的创建
- 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通过返回值返回
- pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值判定,因为读取返回值要比读取线程内的errno变量的开销更小
在Linux中可以通过ps -aL 查看创建的线程
我们可以看到同个进程内的线程的pid是相同的,但是LWP是不同的,因为LWP是线程的id,LWP在内核中使用,和我们用pthread_create获取出来的线程id是不一样的。内核中用LWP来表示线程的唯一。
pthread_create获取出来的线程id是我们用户自己使用的,可以通过pthread_ self()来获取。
那么这个线程id到底是什么呢?
我们使用的所有的线程的函数都不是系统直接提供的,是原生线程库提供的,而原生线程库一定不只会有我们一个进程用,所以原生线程库中一定会存在多个进程创建的多个线程,所以线程库一定要把我们多个进程创建的线程给管理好,所以线程库中会存在描述线程的结构体,结构体中有很多线程的数据(属于哪个进程,线程id等),然后再用数据结构把各个描述线程的结构体管理起来。我们来认识一个系统调用:
它可以通过flags的标识符来表示创建一个进程或者是创建一个轻量级进程(线程),我们看到参数中有一个child_stack的参数,表示我们是可以传一段空间是作为线程的栈空间的,所以我们前面说每个线程有自已的独立栈空间,pthread_create的底层就是封装了这个函数。因此我们每个新线程都会有自己的栈空间,而默认地址空间中的栈由主线程使用。在原生线程库中每个线程和每个线程的数据结构和栈空间还有一些相关的独立的数据放在一起,而我们用户用的线程id就是线程属性在线程库中的地址。
现在理解了线程id后,我们迷惑的应该是线程的局部存储是什么,我们知道对于全局变量来说是被所有线程共享的,但是加了一个__thread修饰一个变量,程序在编译的时候就会为每个线程开辟一段空间专门存储这个变量,也就是说,这个变量每个线程都存在一份,互不干扰。
线程终止
如果需要只终止某个线程而不终止整个进程,可以有三种方法:
- 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
- 线程可以调用pthread_ exit终止自己。
- 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。
pthread_exit
pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
pthread_cancel
线程等待
为什么要进程线程等待?
- 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
- 创建新的线程不会复用刚才退出线程的地址空间。
pthread_join
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
- 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
- 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED(-1)。
- 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参
数。 - 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。
线程分离
一般情况下对于创建的线程我们是需要join的,但是如果我们不关系线程的返回值,那么join就会成为一中负担,这时我们就可以对线程进程分离。即当线程退出时,自动释放线程资源。
pthread_detach
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离,可以通过pthread_self()来获取自己的线程id。
join和分离是冲突的,一个线程不能既是join又是分离的。
如何理解语言中的线程库?
本质就是对原生线程库的封装。
线程中可以进程fork吗?可以进程execl程序替换吗?
线程中是可以fork的,也是可以进程execl程序替换的,但是进行程序替换整个进程的代码都会被替换,可能会影响其他线程的正常运行,比较推荐先fork然后在进程程序替换。
封装线程库
基于上面的接口,我们来模拟实现一下简单版的线程库。
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
#include <unistd.h>template<class T>
using func_t = std::function<void(T)>;template<class T>
class Thread
{
public:Thread(const std::string& name, func_t<T> func, T data) : _name(name), _func(func), _tid(0), _isruning(false), _data(data){}static void* threadRountine(void* attr){Thread* t = static_cast<Thread*>(attr);t->_func(t->_data);}void Start(){int n = pthread_create(&_tid,nullptr,threadRountine,this);if(n == 0) {_isruning = true;}else {std::cerr << "pthread error" << std::endl;}}void Join(){if(!_isruning) return;int n = pthread_join(_tid,nullptr);if(n == 0){_isruning = false;}else {std::cerr << "join error" << std::endl;}}std::string getname(){return _name;}bool isruning(){return _isruning;}
private:std::string _name;pthread_t _tid;bool _isruning;func_t<T> _func;T _data;
};
如果需要返回值可以在成员变量可以加个模板参数在成员变量中定义一个返回值通过join得到就可以,如果调用的函数参数有多个也可以通过类似的方法实现。
相关文章:

线程的概念和控制
文章目录 线程概念线程的优点线程的缺点线程异常线程用途理解虚拟地址 线程控制线程的创建线程终止线程等待线程分离封装线程库 线程概念 什么是线程? 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一…...

PHS树脂(聚对羟基苯乙烯)为KrF光刻胶专用树脂 本土企业具备百公斤级别量产能力
PHS树脂(聚对羟基苯乙烯)为KrF光刻胶专用树脂 本土企业具备百公斤级别量产能力 PHS树脂又称聚对羟基苯乙烯树脂、聚羟基苯乙烯树脂,指以对羟基苯乙烯作为基材制成的光刻胶树脂。与其他光刻胶树脂相比,PHS树脂具有极佳热稳定性、化…...

Python 机器学习 基础 之 数据表示与特征工程 【单变量非线性变换 / 自动化特征选择/利用专家知识】的简单说明
Python 机器学习 基础 之 数据表示与特征工程 【单变量非线性变换 / 自动化特征选择/利用专家知识】的简单说明 目录 Python 机器学习 基础 之 数据表示与特征工程 【单变量非线性变换 / 自动化特征选择/利用专家知识】的简单说明 一、简单介绍 二、单变量非线性变换 三、自…...

uniapp-自定义navigationBar
封装导航栏自定义组件 创建 nav-bar.vue <script setup>import {onReady} from dcloudio/uni-appimport {ref} from vue;const propsdefineProps([navBackgroundColor])const statusBarHeight ref()const navHeight ref()onReady(() > {uni.getSystemInfo({success…...

多式联运奇迹:探索 GPT-4o 的尖端功能
取得的显着进展的DigiOps与人工智能已经标志着重要的里程碑,随着时间的推移塑造了人工智能系统的能力。从早期基于规则系统的出现机器学习和深入学习,人工智能已经发展得更加先进和通用。 生成式预训练 Transformer (GPT) by OpenAI 已特别值得注意。每…...

前端 CSS 经典:好看的标题动画
前言:好看的标题动画实现。 效果: <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><…...

Vue项目打包优化(element+echarts+vue使用cdn)
如何打包查看所有资源大小? 使用插件:webpack-bundle-analyzer 效果图: 安装webpack-bundle-analyzer 第一步,终端执行 npm instatll webpack-bundle-analyzer --save-dev第二步,vue.config.js配置 module.export…...
【ARM 嵌入式 C 入门及渐进 6.1 -- ARMv8 C 内嵌汇编写系统寄存器的函数实现】
请阅读【嵌入式开发学习必备专栏】 文章目录 ARMv8 C 内嵌汇编写系统寄存器 ARMv8 C 内嵌汇编写系统寄存器 在ARMv8架构下,使用C语言结合内嵌汇编实现将一个值写入特定系统寄存器的函数可以按照下面的方法进行。 下面这个示例展示了如何将一个uint64_t类型的值写入…...

ESP32基础应用之使用手机浏览器作为客户端与ESP32作为服务器进行通信
文章目录 1 准备2 移植2.1 softAP工程移植到simple工程中2.2 移植注意事项 3 验证 1 准备 参考工程 Espressif\frameworks\esp-idf-v5.2.1\examples\wifi\getting_started\softAP softAP工程演示将ESP32作为AP,即热点,使手机等终端可以连接参考工程 Esp…...

【课后练习分享】Java用户注册界面设计和求三角形面积的图形界面程序
目录 java编程题(每日一练): 问题一的答案代码如下: 问题一的运行截图如下: 问题二的答案代码如下: 问题二的运行截图如下: java编程题(每日一练): 1.…...

三维空间坐标系变换(旋转平移)
在探究三维空间下的变换前,首先研究二位空间,因为比较直观,再推广到三维空间。 首先应该清楚的一点是:旋转、平移对于坐标系下的点以及坐标系本身而言都是相对的(运动的相对性)。 例如: X O Y …...

OC笔记之foundation框架
OC学习笔记(三) 文章目录 OC学习笔记(三)常用Foundation框架结构体NSRangeNSRange结构体的定义定义 NSRange 的方法打印Range的相关信息NSRange的实际运用查找子字符串返回NSRange结构体 NSPointNSRect NSStringNSString的创建NSS…...

Docker部署springboot包并联通MySQL
Docker部署jar 实现功能 部署springboot下发布的jar包不同docker容器之间通信(如MySQL访问、Redis访问)多个jar包部署 参考文献 Just a moment… Just a moment… https://www.jb51.net/article/279449.htm springboot配置 这里使用多yaml配置文件&…...
多帧激光点云基于标定参数进行融合拼接
1、前言 在三维视觉技术蓬勃发展的今天,点云作为捕获和表示三维环境的基础数据形式,扮演着至关重要的角色。点云融合拼接技术,作为连接孤立点云片段、构建连续、全面三维场景的核心过程,对于自动驾驶、机器人导航、三维建模以及地…...
python数据类型之字符串
目录 1.字符串概念和注意事项 2.字符串内置函数 3.字符串的索引、切片和遍历 4.字符串运算符 5.字符串常用方法 性质判断 开头结尾判断 是否存在某个子串 大小写等格式转化 子串替换 删除两端空白字符 格式化字符串 分割与合并 6.字符串模板 7.exec 函数 8.字符…...

Vue3实战笔记(38)—粒子特效终章
文章目录 前言一、怎样使用官方提供的特效二、海葵特效总结 前言 官方还有很多漂亮的特效,但是vue3只有一个demo,例如我前面实现的两个页面就耗费了一些时间,今天记录一下tsparticles官方内置的几个特效的使用方法,一般这几个就足…...

晶体振荡器
一、晶振与晶体区别 晶振是有源晶振的简称,又叫振荡器,英文名称是oscillator,内部有时钟电路,只需供电便可产生振荡信号;晶体是无源晶振的简称,也叫谐振器,英文名称是crystal,是无极…...

单词可交互的弧形文本
在一个项目中,要求把少儿读本做成电子教材呈现出来,电子书的排版要求跟纸质书一致。其中,英语书有个需求:书中有些不规则排版的文本(如下图所示),当随书音频播放时,被读到的文本要求…...

Linux——进程信号(一)
1.信号入门 1.1生活中的信号 什么是信号? 结合实际红绿灯、闹钟、游戏中的"!"等等这些都是信号。 以红绿灯为例子: 一看到红绿灯我们就知道:红灯停、绿灯行;我们不仅知道它是一个红绿灯而且知道当其出现不同的状况…...
centos9 stream在线安装NVIDIA驱动(rockylinux9.4也成功安装nvidia驱动)
Install NVIDIA Drivers on CentOS Stream 9(rockylinux9.4成功) 主板为技嘉mz72-hb2 显卡为4090 一.Disable Secure Boot From the BIOS 二.Enabling the EPEL Repository on CentOS Stream 9 1.update the DNF package repository cache sudo dnf …...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...

Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...

莫兰迪高级灰总结计划简约商务通用PPT模版
莫兰迪高级灰总结计划简约商务通用PPT模版,莫兰迪调色板清新简约工作汇报PPT模版,莫兰迪时尚风极简设计PPT模版,大学生毕业论文答辩PPT模版,莫兰迪配色总结计划简约商务通用PPT模版,莫兰迪商务汇报PPT模版,…...
DAY 26 函数专题1
函数定义与参数知识点回顾:1. 函数的定义2. 变量作用域:局部变量和全局变量3. 函数的参数类型:位置参数、默认参数、不定参数4. 传递参数的手段:关键词参数5 题目1:计算圆的面积 任务: 编写一…...

uni-app学习笔记三十五--扩展组件的安装和使用
由于内置组件不能满足日常开发需要,uniapp官方也提供了众多的扩展组件供我们使用。由于不是内置组件,需要安装才能使用。 一、安装扩展插件 安装方法: 1.访问uniapp官方文档组件部分:组件使用的入门教程 | uni-app官网 点击左侧…...