热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

基本功:线程上下文切换

由于现在大多计算机都是多核CPU,多线程往往会比单线程更快,更能够提高并发,但提高并发并不意味着启动更多的线程来执行。更多的线程意味着线程创建销毁开销加大、上下文非常频繁,你的程序

由于现在大多计算机都是多核CPU,多线程往往会比单线程更快,更能够提高并发,但提高并发并不意味着启动更多的线程来执行。更多的线程意味着线程创建销毁开销加大、上下文非常频繁,你的程序反而不能支持更高的TPS。可以看另一篇《Java从线程安全到synchronized和Lock探索》

时间片

多任务系统往往需要同时执行多道作业。作业数往往大于机器的CPU数,然而一颗CPU同时只能执行一项任务,如何让用户感觉这些任务正在同时进行呢? 操作系统的设计者 巧妙地利用了时间片轮转的方式

时间片是CPU分配给各个任务(线程)的时间!

思考:单核CPU为何也支持多线程呢?

线程上下文是指某一时间点 CPU 寄存器和程序计数器的内容,CPU通过时间片分配算法来循环执行任务(线程),因为时间片非常短,所以CPU通过不停地切换线程执行。

换言之,单CPU这么频繁,多核CPU一定程度上可以减少上下文切换。

超线程

现代CPU除了处理器核心之外还包括寄存器、L1L2缓存这些存储设备、浮点运算单元、整数运算单元等一些辅助运算设备以及内部总线等。一个多核的CPU也就是一个CPU上有多个处理器核心,就意味着程序的不同线程需要经常在CPU之间的外部总线上通信,同时还要处理不同CPU之间不同缓存导致数据不一致的问题。

超线程这个概念是Intel提出的,简单来说是在一个CPU上真正的并发两个线程,由于CPU都是分时的(如果两个线程A和B,A正在使用处理器核心,B正在使用缓存或者其他设备,那AB两个线程就可以并发执行,但是如果AB都在访问同一个设备,那就只能等前一个线程执行完后一个线程才能执行)。实现这种并发的原理是 在CPU里加了一个协调辅助核心,根据Intel提供的数据,这样一个设备会使得设备面积增大5%,但是性能提高15%~30%。

上下文切换

  • 线程切换,同一进程中的两个线程之间的切换
  • 进程切换,两个进程之间的切换
  • 模式切换,在给定线程中,用户模式和内核模式的切换
  • 地址空间切换,将虚拟内存切换到物理内存

CPU切换前把当前任务的状态保存下来,以便下次切换回这个任务时可以再次加载这个任务的状态,然后加载下一任务的状态并执行。任务的状态保存及再加载, 这段过程就叫做上下文切换。

每个线程都有一个程序计数器(记录要执行的下一条指令),一组寄存器(保存当前线程的工作变量),堆栈(记录执行历史,其中每一帧保存了一个已经调用但未返回的过程)。

寄存器 是 CPU 内部的数量较少但是速度很快的内存(与之对应的是 CPU 外部相对较慢的 RAM 主内存)。寄存器通过对常用值(通常是运算的中间值)的快速访问来提高计算机程序运行的速度。

程序计数器是一个专用的寄存器,用于表明指令序列中 CPU 正在执行的位置,存的值为正在执行的指令的位置或者下一个将要被执行的指令的位置。

  1. 挂起当前任务(线程/进程),将这个任务在 CPU 中的状态(上下文)存储于内存中的某处
  2. 恢复一个任务(线程/进程),在内存中检索下一个任务的上下文并将其在 CPU 的寄存器中恢复
  3. 跳转到程序计数器所指向的位置(即跳转到任务被中断时的代码行),以恢复该进程在程序中

《基本功:线程上下文切换》

线程上下文切换会有什么问题呢?

上下文切换会导致额外的开销,常常表现为高并发执行时速度会慢串行,因此减少上下文切换次数便可以提高多线程程序的运行效率。

  • 直接消耗:指的是CPU寄存器需要保存和加载, 系统调度器的代码需要执行, TLB实例需要重新加载, CPU 的pipeline需要刷掉
  • 间接消耗:指的是多核的cache之间得共享数据, 间接消耗对于程序的影响要看线程工作区操作数据的大小

切换查看

Linux系统下可以使用vmstat命令来查看上下文切换的次数, 其中cs列就是指上下文切换的数目一般情况下, 空闲系统的上下文切换每秒大概在1500以下

《基本功:线程上下文切换》

线程调度

抢占式调度

指的是每条线程执行的时间、线程的切换都由系统控制,系统控制指的是在系统某种运行机制下,可能每条线程都分同样的执行时间片,也可能是某些线程执行的时间片较长,甚至某些线程得不到执行的时间片。在这种机制下,一个线程的堵塞不会导致整个进程堵塞。

java使用的线程调使用抢占式调度,Java中线程会按优先级分配CPU时间片运行,且优先级越高越优先执行,但优先级高并不代表能独自占用执行时间片,可能是优先级高得到越多的执行时间片,反之,优先级低的分到的执行时间少但不会分配不到执行时间。

《基本功:线程上下文切换》

协同式调度

指某一线程执行完后主动通知系统切换到另一线程上执行,这种模式就像接力赛一样,一个人跑完自己的路程就把接力棒交接给下一个人,下个人继续往下跑。线程的执行时间由线程本身控制,线程切换可以预知,不存在多线程同步问题,但它有一个致命弱点:如果一个线程编写有问题,运行到一半就一直堵塞,那么可能导致整个系统崩溃。

《基本功:线程上下文切换》

线程让出cpu的情况

  • 当前运行线程主动放弃CPU,JVM暂时放弃CPU操作(基于时间片轮转调度的JVM操作系统不会让线程永久放弃CPU,或者说放弃本次时间片的执行权),例如调用yield()方法。
  • 当前运行线程因为某些原因进入阻塞状态,例如阻塞在I/O上
  • 当前运行线程结束,即运行完run()方法里面的任务

引起线程上下文切换的因素

  • 当前执行任务(线程)的时间片用完之后,系统CPU正常调度下一个任务
  • 中断处理,在中断处理中,其他程序”打断”了当前正在运行的程序。当CPU接收到中断请求时,会在正在运行的程序和发起中断请求的程序之间进行一次上下文切换。中断分为硬件中断和软件中断,软件中断包括因为IO阻塞、未抢到资源或者用户代码等原因,线程被挂起。
  • 用户态切换,对于一些操作系统,当进行用户态切换时也会进行一次上下文切换,虽然这不是必须的。
  • 多个任务抢占锁资源,在多任务处理中,CPU会在不同程序之间来回切换,每个程序都有相应的处理时间片,CPU在两个时间片的间隔中进行上下文切换

因此优化手段有:

  • 无锁并发编程,多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash取模分段,不同的线程处理不同段的数据
  • CAS算法,Java的Atomic包使用CAS算法来更新数据,而不需要加锁
  • 使用最少线程
  • 协程,单线程里实现多任务的调度,并在单线程里维持多个任务间的切换

合理设置线程数目既可以最大化利用CPU,又可以减少线程切换的开销。《基本功:线程上下文切换》《基本功:线程上下文切换》

  • 高并发,低耗时的情况,建议少线程。
  • 低并发,高耗时的情况:建议多线程。
  • 高并发高耗时,要分析任务类型、增加排队、加大线程数

推荐阅读
  • 兆芯X86 CPU架构的演进与现状(国产CPU系列)
    本文详细介绍了兆芯X86 CPU架构的发展历程,从公司成立背景到关键技术授权,再到具体芯片架构的演进,全面解析了兆芯在国产CPU领域的贡献与挑战。 ... [详细]
  • 高端存储技术演进与趋势
    本文探讨了高端存储技术的发展趋势,包括松耦合架构、虚拟化、高性能、高安全性和智能化等方面。同时,分析了全闪存阵列和中端存储集群对高端存储市场的冲击,以及高端存储在不同应用场景中的发展趋势。 ... [详细]
  • Python错误重试让多少开发者头疼?高效解决方案出炉
    ### 优化后的摘要在处理 Python 开发中的错误重试问题时,许多开发者常常感到困扰。为了应对这一挑战,`tenacity` 库提供了一种高效的解决方案。首先,通过 `pip install tenacity` 安装该库。使用时,可以通过简单的规则配置重试策略。例如,可以设置多个重试条件,使用 `|`(或)和 `&`(与)操作符组合不同的参数,从而实现灵活的错误重试机制。此外,`tenacity` 还支持自定义等待时间、重试次数和异常处理,为开发者提供了强大的工具来提高代码的健壮性和可靠性。 ... [详细]
  • 开发日志:201521044091 《Java编程基础》第11周学习心得与总结
    开发日志:201521044091 《Java编程基础》第11周学习心得与总结 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • 2020年9月15日,Oracle正式发布了最新的JDK 15版本。本次更新带来了许多新特性,包括隐藏类、EdDSA签名算法、模式匹配、记录类、封闭类和文本块等。 ... [详细]
  • HTTP(HyperTextTransferProtocol)是超文本传输协议的缩写,它用于传送www方式的数据。HTTP协议采用了请求响应模型。客服端向服务器发送一 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 本文总结了一些开发中常见的问题及其解决方案,包括特性过滤器的使用、NuGet程序集版本冲突、线程存储、溢出检查、ThreadPool的最大线程数设置、Redis使用中的问题以及Task.Result和Task.GetAwaiter().GetResult()的区别。 ... [详细]
  • Android 构建基础流程详解
    Android 构建基础流程详解 ... [详细]
  • 在《Linux高性能服务器编程》一书中,第3.2节深入探讨了TCP报头的结构与功能。TCP报头是每个TCP数据段中不可或缺的部分,它不仅包含了源端口和目的端口的信息,还负责管理TCP连接的状态和控制。本节内容详尽地解析了TCP报头的各项字段及其作用,为读者提供了深入理解TCP协议的基础。 ... [详细]
  • 本文深入解析了JDK 8中HashMap的源代码,重点探讨了put方法的工作机制及其内部参数的设定原理。HashMap允许键和值为null,但键为null的情况只能出现一次,因为null键在内部通过索引0进行存储。文章详细分析了capacity(容量)、size(大小)、loadFactor(加载因子)以及红黑树转换阈值的设定原则,帮助读者更好地理解HashMap的高效实现和性能优化策略。 ... [详细]
  • 本文探讨了如何通过编程手段在Linux系统中禁用硬件预取功能。基于Intel® Core™微架构的应用性能优化需求,文章详细介绍了相关配置方法和代码实现,旨在帮助开发人员有效控制硬件预取行为,提升应用程序的运行效率。 ... [详细]
  • 本文深入探讨了Java多线程环境下的同步机制及其应用,重点介绍了`synchronized`关键字的使用方法和原理。`synchronized`关键字主要用于确保多个线程在访问共享资源时的互斥性和原子性。通过具体示例,如在一个类中使用`synchronized`修饰方法,展示了如何实现线程安全的代码块。此外,文章还讨论了`ReentrantLock`等其他同步工具的优缺点,并提供了实际应用场景中的最佳实践。 ... [详细]
  • 本指南详细介绍了在Linux环境中高效连接MySQL数据库的方法。用户可以通过安装并使用`mysql`客户端工具来实现本地连接,具体命令为:`mysql -u 用户名 -p 密码 -h 主机`。例如,使用管理员账户连接本地MySQL服务器的命令为:`mysql -u root -p pass`。此外,还提供了多种配置优化建议,以确保连接过程更加稳定和高效。 ... [详细]
author-avatar
判官-包公_529
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有