并发与运行时调度(1) - 操作系统线程调度

作为一种提高计算机多任务处理能力的方法,多线程编程已经得到了广泛的应用,同时主流编程语言也已提供完备的支持。

然而由于任务类型、运行环境的变化,多线程程序的执行效率存在巨大的差别。因此有必要理解操作系统对于多线程任务的调度模型和相关算法,以此加深对底层运行机制的理解,产出更优秀的程序设计。

本文中的相关模型均以linux系统为例。

指令执行

首先,我们需要了解任何语言写出的代码如何被操作系统执行的。

任意语言编写的代码都将被转换成一系列汇编指令(instructions)执行,指令内容基本为对寄存器的操作,此时操作系统中的program counter(instruction pointer)将负责记录下一条指令的地址,参考下图:

图片

 

线程调度

接下来思考这样一个问题,程序中创建线程的数目往往大于cpu core的数目,那么操作系统是如何做到多线程并发运行的?

事实上,在多线程程序的运行中,每个线程会在一段时间内获得cpu的使用权,直到因为任务自身原因或操作系统调度原因让出cpu使用权为止。

依据任务占有cpu的形式不同,我们可以将操作系统调度方式分为抢占式和非抢占式两种(preemptive vs. non-preemptive)。linux采用的是抢占式调度算法结合线程优先级,避免单个线程长时间占用cpu。

在linux中,线程实现层面对应的是lwp(轻量级进程),在内核中对应一个内核进程(1:1模型,部分编程语言如go实现了m:n模型),因此linux中的线程调度是由内核按照进程调度来进行的。

线程状态

在linux系统中,线程的状态如下:

waiting

线程已经停止执行并且正在等待所需资源以继续执行。通常,当线程等待网络响应、进行系统调用、尝试获取同步锁时将进入该状态,由此带来的延时也是系统性能劣化的重要根源之一。

runnable

线程已经获取了所需资源,等待被调度到cpu core运行。当大量线程处于该状态时,操作系统将依据线程优先级选择其中一个线程运行,因此如果过多的线程处于该状态,将缩短单个线程的运行时间,同样导致系统性能劣化。

executing

不解释,线程运行中。

linux系统中线程的状态转移图如下:

线程上下文切换

前面已经谈到,线程在操作系统中运行时可能由于网络io调用或主动出让时间片将cpu运行时间交给其它线程。这种交换cpu core上的线程的行为被称为上下文切换(context switch)。

上下文切换是一种成本高昂的行为,计算机各io操作的延时参考如下:

https://people.eecs.berkeley.edu/~rcs/research/interactive_latency.html

在上下文切换中,系统资源消耗主要来自两方面:

1.上下文切换时需要保存的栈信息、寄存器信息、pc信息

通常耗费1000-1500ns,现代cpu core每ns可执行12条指令,因此一次上下文切换相当于执行了12k-18k次指令

2.cpu cache miss导致的sram重新填充

cpu cache失效后需要进行重新填充,而内存的访问耗时相当于cpu cache的10倍以上

对于cpu bound类型的任务来说,增加系统中的线程数目很可能无法提高系统的performance,因为有大量的系统资源被消耗在上下文切换中。

对于io bound类型的任务来说,适当增加系统中的线程数目有利于提高系统的performance,然而考虑到线程数目的增加将导致sheduling overhead消耗时间在cpu运行时间中的占比上升,过多的线程仍然将导致系统性能下降。此时可考虑io多路复用等技术代替传统的bio提升系统性能。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值