Linux7 线程(一)

线程

  • 1. 概念
  • 2. 库函数
    • 线程库
    • 创建线程
    • 线程ID
    • 线程终止
    • 线程等待
    • 线程分离
  • 3. 线程的互斥
    • 相关概念
    • 临界资源
    • 互斥量 - mutex
    • 初始化互斥量
      • 静态分配
      • 动态分配
    • 销毁互斥量
    • 互斥量加锁
    • 互斥量解锁
    • 死锁
      • 概念
      • 死锁的四个必要条件
      • 避免死锁
      • 避免死锁算法
  • 4. 线程的同步
    • 条件变量
    • 初始化条件变量
      • 静态分配
      • 动态分配
    • 销毁条件变量
    • 等待条件满足
    • 唤醒等待
  • 5. 线程的优点
  • 6. 线程缺点
  • 7. 线程异常
  • 8. 线程用途
  • 9. 一些问题
    • 已经有多进程了,为什么要有多线程?
    • 为什么线程的调度成本低?
    • 关于线程的私有部分
    • 关于线程和寄存器
    • CPU寄存器只有一套,寄存器里面的数据可以有多套

1. 概念

  • 一个程序里的一个执行路线就叫做线程,线程是一个进程内部的控制序列(执行流)
  • 线程是CPU调度的基本单位,进程是承担分配系统资源的基本实体
  • 线程有线程id,优先级,状态,上下文,连接属性等
  • 一个进程多个线程属于同一个pid,但每个线程都有自己的LWP
  • Linux下使用进程模拟线程(CPU不需要区分看到的task_struct是进程还是线程,都属于执行流(轻量级进程))
ps -aL	#查看线程情况

2. 库函数

线程库

  • 调用线程库需要引入头文件<pthread.h>
  • 链接这些线程函数库要使用编译器命令 -lpthread 选项

创建线程

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(start_routine)(void), void *arg);

  • 功能:创建一个新的线程
  • thread:输出型参数,用于接受创建完的线程ID
  • attr:设置线程的属性,attr为NULL表示使用默认属性
  • start_routine:是个函数地址,线程启动后要执行的函数,这个函数必须返回值为void,参数为void**
  • arg:传给线程启动函数的参数
  • 返回值:成功0,失败错误码

关于返回错误:

  • 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
  • pthreads函数出错时不会设置全局变量errno,而是将错误代码通过返回值返回。
  • pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值判断,因为读取返回值要比读取线程内的errno变量的开销更小。

当线程pthread_create的时候,它就会自动去执行任务。但有时候会因为线程只是创建了一个线程,然后就立刻返回了导致次线程无法完成任务。

  1. 主线程可以通过sleep延时来进行等待子线程完成任务
  2. 主线程也可以通过后面的“线程等待(pthread_join)”来进行阻塞式等待子线程完成任务

线程ID

  • pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
  • 用ps查看的线程ID属于进程调度范畴,因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
  • pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
  • 线程库NPTL提供了对应函数,可以获得线程自身的ID

pthread_t pthread_self(void);

pthread_t 类型的线程ID,本质上就是进程地址空间上的一个地址

线程终止

  1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
  2. 线程可以调用pthread_ exit终止自己。
  3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

void pthread_exit(void *value_ptr);

  • 功能:线程终止
  • value_ptr是一个指向返回值的指针。这个返回值可以被其他线程通过pthread_join函数获取。返回值的类型是void*,这意味着它可以是任何类型的数据指针,例如,可以是一个指向结构体的指针,其中结构体中包含了线程的执行结果等信息。如果不需要返回任何值,可以将retval设置为NULL。
  • 需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出

int pthread_cancel(pthread_t thread);

  • 功能:取消一个执行中的线程
  • thread:线程ID
  • 返回值:成功返回0,失败错误码

线程等待

为什么需要线程等待

  • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
  • 创建新的线程不会复用刚才退出线程的地址空间。

int pthread_join(pthread_t thread, void **value_ptr);

  • 功能:等待线程结束
  • thread:线程ID
  • value_ptr:输出型参数,它指向一个指针,后者指向线程的返回值
  • 返回值:成功0,失败错误码

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

  1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_CANCELED。
  3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

线程分离

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  • 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源

int pthread_detach(pthread_t thread);

  • 功能:把对应线程分离
  • thread:分离线程的线程id
  • 返回值:成功为0,失败错误码

注意:

  • 一个线程分离后是无法被join的
  • 线程分离后,如果出现异常,也会导致进程挂掉

3. 线程的互斥

相关概念

  • 临界资源:多线程执行流共享的资源就叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
  • 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
  • 原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

临界资源

  • 对临界资源进行保护,本质是对临界区代码进行保护
  • 对有所有资源进行访问,本质就是通过代码进行访问
  • 保护资源,本质就是想办法吧访问资源的代码保护起来
  • 执行流在非临界区可能是并行,而到了临界区就变成串行

互斥量 - mutex

  • 多个执行流访问共享的资源必须要有互斥行为 —— 当代码进入临界区执行时,不允许其他线程进入该临界区。
  • 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
  • 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。
    那么做到这些需要一把锁,Linux提供的这把锁叫互斥量mutex

初始化互斥量

静态分配

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;(静态分配)

动态分配

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrictattr);

  • mutex:输出型参数,表示要初始化的互斥量
  • attr:互斥锁属性,不关注设置为NULL
  • 返回值:成功返回0,失败错误码

销毁互斥量

  • 使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

互斥量加锁

加锁的时候,会遇到这些情况:

  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。
int pthread_mutex_lock(pthread_mutex_t *mutex);	//成功返回0,失败错误码

互斥量解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);	//成功返回0,失败错误码

死锁

概念

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态。

死锁的四个必要条件

  • 互斥条件:一个资源每次只能被一个执行流使用
  • 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
  • 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

避免死锁

  • 破坏死锁的四个必要条件
  • 加锁顺序一致
  • 避免锁未释放的场景
  • 资源一次性分配

避免死锁算法

  • 死锁检测算法
  • 银行家算法

4. 线程的同步

条件变量

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。
同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问s题,叫做同步。

初始化条件变量

静态分配

pthread_cond_t cond = PTHREAD_COND_INITALIZER;

动态分配

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrictattr);

  • 功能:初始化条件变量
  • cond:输出型参数,表示要初始化的条件变量
  • attr:条件变量属性,不关注可以设置NULL

销毁条件变量

int pthread_cond_destroy(pthread_cond_t *cond)

  • 功能:销毁条件变量
  • cond:表示要删除的条件变量

等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

  • 功能:等待某个条件变量满足
  • cond:在这个条件变量上等待
  • mutex:互斥量,传入这个函数之前,这个互斥量必须是加锁状态

注意:为什么等待后面要加锁?

  • 被调用的时候,除了让自己进行排队,同时也会原子性的释放传入的锁。
  • 返回时,就不在临界区了,必须参与锁竞争,重新上锁。
  • 比方两个线程都在条件变量这里等条件变量放开,其他线程一次广播唤醒两个线程,这两个线程重新开始竞争。
  • 其中一个线程抢到锁,另一个线程只能继续在条件变量这里等锁。

唤醒等待

//通过此条件变量,唤醒所有线程
int pthread_cond_broadcast(pthread_cond_t *cond);	//通过此条件变量,唤醒一个线程
int pthread_cond_signal(pthread_cond_t *cond);	

5. 线程的优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

6. 线程缺点

  • 多个线程都是唯一的进程pid,一个线程异常,所有线程包括进程都会挂掉;而多进程就不会,每个进程具有独立性
  • 性能损失
  • 健壮性降低:多线程之间缺乏保护,编写需要考虑周全
  • 缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
  • 编程难度高:编写与调试一个多线程程序比单线程程序困难得多

7. 线程异常

  • 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出

8. 线程用途

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

9. 一些问题

已经有多进程了,为什么要有多线程?

  • 进程创建成本非常高,线程创建成本非常低(创建PCB,把已有进程的资源给它)
  • 线程调度成本低,删除一个线程成本也很低

为什么线程的调度成本低?

  • CPU集成了cache,进程中线程切换少了更新cache中内容这一步
  • cache可以预先加载内存的内容并保存,如果命中则不需访问内存存

关于线程的私有部分

  • 进程是资源分配的基本单位,线程是调度的基本单位,线程共享进程数据,也有自己一部分数据

共享部分:

  1. 文件描述符表
  2. 每种信号的处理方式
  3. 当前工作目录
  4. 用户id和组id

线程私有部分:

  1. 一组寄存器(执行流上下文数据)
  2. (多个线程启动可能是无序的,如果多个线程用同一个栈,那必定无法随意出栈),因此每个线程都必须有自己独立的栈空间,此空间用于存储函数调用的局部变量、函数参数和返回地址等信息,且无法与其他线程共享。
  3. 线程ID
  4. errno
  5. 信号屏蔽字
  6. 调度优先级

关于线程和寄存器

  • 每个线程都有一组寄存器,这是因为寄存器是 CPU 内部用于快速存储和操作数据的单元。在多线程环境下,当线程进行切换时,需要保存和恢复线程的执行状态,而寄存器状态是执行状态的重要组成部分。

线程切换与寄存器保存恢复

  • 当操作系统进行线程切换时,它需要将当前正在执行线程的上下文(包括寄存器的值)保存起来。这个过程就像是在阅读一本书时,你把当前看到的页码(类似于程序计数器,是一个寄存器)、书中重点标记的内容(其他寄存器存储的数据)等信息记录下来。然后,当这个线程下次被调度执行时,操作系统会把之前保存的寄存器值恢复到 CPU 的寄存器中,这样线程就可以从上次中断的地方继续执行,就好像你再次翻开书,根据记录的页码和重点继续阅读一样。

独立性和隔离性:

  • 寄存器对于每个线程是私有的,这确保了每个线程在执行时可以独立地使用寄存器来存储自己的临时计算结果、函数参数、局部变量等信息,不会受到其他线程的干扰。

提高执行效率:

  • 由于寄存器的访问速度比内存快得多,线程能够直接使用自己私有的寄存器来快速执行各种操作,避免了因为共享寄存器而可能产生的冲突和等待,从而提高了整个系统的执行效率。

CPU寄存器只有一套,寄存器里面的数据可以有多套

  • 多个线程数据看起来放在一套公共寄存器中,但属于线程私有,当他被切换时,他要带走自己的数据回来的时候,会恢复
  1. CPU的寄存器只有一套,被所有的线程共享,但是寄存器里面的数据,属于执行流的上下文,属于执行流的私有数据
  2. CPU在指向代码的时候,一定要有对应的执行载体 – 线程&&进程
  3. 数据在内存中,被所有线程共享
    结论:把数据从内存移动到CPU中,本质就是把数据从共享,变成线程私有

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

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

相关文章

html的week控件 获取周(星期)的第一天(周一)和最后一天(周日)

html的week控件 获取周(星期)的第一天(周一)和最后一天(周日) <input type"week" id"week" class"my-css" value"ViewBag.DefaultWeek" /><script> function PageList() { var dateStrin…

计算机网络常见面试题(一):TCP/IP五层模型、TCP三次握手、四次挥手,TCP传输可靠性保障、ARQ协议

文章目录 一、TCP/IP五层模型&#xff08;重要&#xff09;二、应用层常见的协议三、TCP与UDP3.1 TCP、UDP的区别&#xff08;重要&#xff09;3.2 运行于TCP、UDP上的协议3.3 TCP的三次握手、四次挥手3.3.1 TCP的三次握手3.3.2 TCP的四次挥手3.3.3 随机生成序列号的原因 四、T…

Redis集群模式之Redis Sentinel vs. Redis Cluster

在分布式系统环境中&#xff0c;Redis以其高性能、低延迟和丰富的数据结构而广受青睐。随着数据量的增长和访问需求的增加&#xff0c;单一Redis实例往往难以满足高可用性和扩展性的要求。为此&#xff0c;Redis提供了两种主要的集群模式&#xff1a;Redis Sentinel和Redis Clu…

python opencv3

三、图像预处理2 1、图像滤波 为图像滤波通过滤波器得到另一个图像。也就是加深图像之间的间隙&#xff0c;增强视觉效果&#xff1b;也可以模糊化间隙&#xff0c;造成图像的噪点被抹平。 2、卷积核 在深度学习中&#xff0c;卷积核越大&#xff0c;看到的信息越多&#xff0…

JAVA后端生成图片滑块验证码 springboot+js完整案例

前言 现在大部分网部都是图片滑块验证码&#xff0c;这个得要与后端联动起来才是确保接口安全性 通过我们系统在发送手机短息时都会选进行滑块验证&#xff0c;但是我们要保证发送短息接口的全安&#xff0c;具体路思如下 那么这个滑块的必须是与后端交互才能保证安全性&…

【因果分析方法】MATLAB计算Liang-Kleeman信息流

【因果分析方法】MATLAB计算Liang-Kleeman信息流 1 Liang-Kleeman信息流2 MATLAB代码2.1 函数代码2.2 案例参考Liang-Kleeman 信息流(Liang-Kleeman Information Flow)是由 Liang 和 Kleeman 提出的基于信息论的因果分析方法。该方法用于量化变量之间的因果关系,通过计算信息…

在 Oracle Linux 8.9 上安装Oracle Database 23ai 23.5

在 Oracle Linux 8.9 上安装Oracle Database 23ai 23.5 1. 安装 Oracle Database 23ai2. 连接 Oracle Database 23c3. 重启启动后&#xff0c;手动启动数据库4. 重启启动后&#xff0c;手动启动 Listener5. 手动启动 Pluggable Database6. 自动启动 Pluggable Database7. 设置开…

Ubuntu23.10下解决C语言调用mysql.h问题

Ubuntu23.10下解决C语言调用mysql.h问题 导语环境准备问题和解决方案总结参考文献 导语 在学习C语言和MySQL的调用的时候遇到包和版本的问题&#xff0c;由于使用的书很老&#xff08;10年的&#xff09;&#xff0c;因此很多MySQL的包已经过时&#xff0c;在查找很多资料和询…

【JAVA毕业设计】基于Vue和SpringBoot的微服务在线教育系统

博主说明&#xff1a;本文项目编号 T 060 &#xff0c;文末自助获取源码 \color{red}{T060&#xff0c;文末自助获取源码} T060&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…

Spring Boot编程训练系统:技术实现与案例分析

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

在 ASP.NET Core 6.0 中使用 Swagger/OpenAPI 丰富 Web API 文档

示例代码&#xff1a;https://download.csdn.net/download/hefeng_aspnet/89961435 介绍 在选择或尝试与 API 集成之前&#xff0c;大多数开发人员都会查看其 API 文档。保持 API 文档更新以反映软件更改是一项挑战&#xff0c;需要时间和精力。对于 Web API&#xff0c;我们…

tensorflow案例5--基于改进VGG16模型的马铃薯识别,准确率提升0.6%,计算量降低78.07%

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 本次采用VGG16模型进行预测&#xff0c;准确率达到了98.875&#xff0c;但是修改VGG16网络结构&#xff0c; 准确率达到了0.9969&#xff0c;并且计算量…

Spring Boot编程训练系统:构建可扩展的应用

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了编程训练系统的开发全过程。通过分析编程训练系统管理的不足&#xff0c;创建了一个计算机管理编程训练系统的方案。文章介绍了编程训练系统的系统分析部分&…

Java-sec-code-SSRF攻击

Java-sec-code&#xff08;SSRF攻击&#xff09; java-sec-code平台中也内置了SSRF攻击案例&#xff0c;我们来看看SSRF漏洞代码是什么样的。 案例1 直接从url参数接收数据&#xff0c;但是未进行任何检查和校验。 通过调用httpUtil.URLConnection方法&#xff0c;建立URL对…

docker基础:搭建centos7(详见B站泷羽sec)

docker的简单学习&#xff1a; sudo apt-get update //这个命令让系统检查有没有新软件 sudo apt-get install docker.io //安装 Docker sudo docker version //查看是否安装成功&#xff0c;显示docker的版本信息 启用Docker 启…

RNN(循环神经网络)详解

1️⃣ RNN介绍 前馈神经网络&#xff08;CNN&#xff0c;全连接网络&#xff09;的流程是前向传播、反向传播和参数更新&#xff0c;存在以下不足&#xff1a; 无法处理时序数据&#xff1a;时序数据长度一般不固定&#xff0c;而前馈神经网络要求输入和输出的维度是固定的&a…

解剖C++模板(2) —— 模板匹配规则及特化

众所周知&#xff0c;模板声明部分的尖括号中的内容是声明模板形参&#xff0c;而调用模板时的尖括号是给模板传参。然而这样理解仅仅停留于现象&#xff0c;只是将模板形参传参和函数传参的过程划等号了。C 的函数重载匹配并非真的进行匹配&#xff0c;因为函数名修饰规则导致…

题目讲解15 合并两个排序的链表

原题链接&#xff1a; 合并两个排序的链表_牛客题霸_牛客网 思路分析&#xff1a; 第一步&#xff1a;写一个链表尾插数据的方法。 typedef struct ListNode ListNode;//申请结点 ListNode* BuyNode(int x) {ListNode* node (ListNode*)malloc(sizeof(ListNode));node->…

高性能Web网关:OpenResty 基础讲解

一&#xff1a;概述 OpenResty是由国人章亦春开发的一个基于Nginx的可伸缩的Web平台。 openresty 是一个基于 nginx 与 lua 的高性能 web 平台&#xff0c;其内部集成了大量精良的 lua 库、第三方模块以及大数的依赖项。用于方便搭建能够处理超高并发、扩展性极高的动态 web 应…

OceanBase JDBC (Java数据库连接)的概念、分类与兼容性

本章将介绍 OceanBase JDBC的 概念与分类&#xff0c;已帮助使用 JDBC 的用户及技术人员更好的 了解JDBC&#xff0c;以及 OceanBase JDBC在与 MySQL 及 Oracle 兼容性方面的相关能力。 一、JDBC 基础 1.1 JDBC 的概念 JDBC 一般指 Java 数据库连接。Java 数据库连接&#xf…