并发编程中多线程的调度,线程上下文切换,线程数多少合适

本文探讨了进程与线程在操作系统中的角色,CPU时间片的分配,以及多线程对系统性能的影响。指出线程数并非越多越好,而应根据系统环境和CPU核心数适当调整,一般推荐为逻辑CPU的2到3倍。过度增加线程数可能导致上下文切换开销增大,影响系统整体效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

几个知识点
  • 进程是操作系统分配资源的基本单位,很明显在 /proc/pid目录下你可以看到每一个进程的详细信息,资源情况,但是你却找不到线程的资源信息,那是因为线程是共享了进程的资源。
  • 线程(task)是操作系统调度的基本单位,我们打开top命令看到的 tasks 数就是线程数。
  • CPU时间片是由操作系统来调度和管理的,分配到同一个核心上的线程有很多,为了公平起见,操作系统只允许每个线程运行一定的时间(比如100ms),超过了这个时间此线程就会被切出来,放到就绪队列的尾部,等待下一次被执行,这就是所谓的上下文切换。
  • 一个操作系统自身就运行着很多线程,当然很多线程都处于sleeping状态,你打开top就能看到,running 个数就决定了CPU是否在忙碌。
  • 在多核操作系统上,可以并行运行多个任务,可以理解为有多个消费者。
  • 当需要运行的任务数小于等于逻辑CPU的个数,那么就不需要任务切换。
  • 各个逻辑CPU的使用情况并不是均等的。
谁负责调度

现代CPU都是多核的,如何充分利用多核就是操作系统的职责,不同的操作系统都有自己的线程调度实现,编程语言通过调用系统API来创建线程,并最终交给操作系统来调度,而操作系统的线程调度算法和策略就有可能各不相同。

线程上下文

线程上下文(Thread Context)在多线程编程中,指的是一个线程的当前状态和执行环境。这包括线程的寄存器值、栈信息、程序计数器等关键数据。

具体来说,线程上下文涵盖了CPU在执行特定线程时所需的所有信息,以便在需要时能够恢复到该线程的执行状态,线程说到底就是一个函数,因此主要包含以下信息。

  • 寄存器值:CPU内部的寄存器用于存储运算的中间结果、指令指针等信息,这些值在线程切换时需要被保存和恢复。
  • 栈信息:每个线程都有自己的调用栈,用于存储局部变量、函数参数、返回地址等信息。线程上下文切换时,这些栈信息也需要被妥善管理。
  • 程序计数器:程序计数器是一个专用的寄存器,用于指示CPU当前正在执行的指令位置。在线程切换时,程序计数器的值会被更新,以指向新线程的执行起点。
线程切换

线程切换实际上是上下文切换的过程。当需要从线程A切换到线程B时,系统会先挂起线程A,并将其上下文(包括寄存器值、栈信息、程序计数器等)保存到内存中。然后从内存中检索线程B的上下文,恢复寄存器的值,设置函数栈指针,设置程序计数器,将指令送入CPU开始执行。所以,切换线程也是会消耗系统时间。

线程调度

虽然调度策略可以有不同的实现,但是有一个基本法则,那就是公平和效率。

对于CPU来说,它的L1/L2/L3三级缓存是个利器,且每个核都有自己的L1/L2,使用MESI协议来进行多核之间的缓存同步,那么如何充分利用这个缓存呢,那就需要使程序尽量固定在一个Core上,而不要切换Core,如果线程上个时刻在Core0上运行,留下了一些缓存,而下个时刻又跑到Core1上运行,那Core0的缓存就失效了。

所以,如果只是个单线程的程序,操作系统就会选择一个负载小的Core,将其固定的运行在上面。

如果你的线程数小于等于CPU核数,操作系统也会聪明的让线程分摊在不同核上,也充分利用了多核。

如果线程数大于CPU核数,线程的调度策略就开始工作了,这个时候就是八个锅四个盖的游戏,调度策略很复杂,它需要规避恶意程序(创建了很多线程试图大量抢占CPU运行时间),也要兼顾长短(有的任务运行时间比较短,但是它却排在很后面),还要使时间片的长度可变。

操作系统为每一个核维护了一个任务队列,尽量使这些任务固定在同一个核上运行,但是兼顾每个核的负载来动态调整。

调度开销

主要就是线程的上下文切换,但是就绪线程如果非常多比如几千上万,那么不管它使用什么数据结构来存储这些相关的数据,从宏观上来讲,量大了肯定会导致查找和修改变慢,所以线程越多会使单位时间内(比如1秒)花在线程切换上的时间变多,相对应的执行任务的时间变短了,比如在线程少的时候一秒可以切换十次线程,在线程多的时候一秒只能切换九次线程。

增加线程数意味着什么

有人说,线程数太多会导致上下文切换变得更频繁,进而使效率降低。我觉得这个观点不对。对于一个正常的运行着的操作系统,活跃的线程数可能是CPU核心数的几倍或十几倍,操作系统按照时间片来切换任务,也就是说,任务的上下文切换本来就一直在进行,即使我不启动我的程序。所以,并不是说我的程序的线程数多了,这个切换过程就多了。但是我的程序开启了太多线程,会导致其他程序都很难得到CPU资源。

我们往往想通过启动更多的线程来使程序更加有效率,本质上是使当前程序抢占到更多的CPU执行时间。

假设我的操作系统比较安静,正在运行的任务只有10个,此时我的程序有一个线程,那么根据CPU的时间片轮转原则,我的程序总共占用 1/11 的CPU时间,4个线程对应 4/14,100个线程对应 100/110,可见是无限接近于1的,而且越往后面线程数的增加对CPU的占有率的影响越有限,并且,线程数还跟其他的任务的总数是有关系的,比如将10换成1,结果又不一样。所以线程数并不是越多越好,而是取决于整个的系统环境和调度策略,上文也说了,操作系统也会在一定程度上规避这种流氓行为。

这也是为什么在创建多线程的时候线程数最好不要超过CPU核数,因为太多了并不能提高CPU的使用效率,注意,这里说的是CPU的使用效率,而不是你的程序的运行速度。如果是io密集型的程序,比如读写数据库,大部分时间都在进行io操作,cpu空闲时间很多,我们可以将线程数设置为2倍或3倍的核数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值