- 本文从两点进行分析:
- 线程调度
- 内存占用
- 文章很多内容都是仅凭个人认知推测, 并未进行实时考证, 不过个人感觉应该有参考价值(不然也不会写了)
线程调度
linux
进程调度的时机
进程不再处于
TASK_RUNNING
状态- 如: 由于等待IO而阻塞(
TASK_UNINTERRUPTIBLE
),或者因等待资源和特定事件而休眠(TASK_INTERRUPTIBLE
),又或者被debug/trace设置为TASK_STOPPED
/TASK_TRACED
状态。
- 如: 由于等待IO而阻塞(
周期性的时钟中断处理函数中, 判断进程被抢占(preemption)(取决于
preemption
算法)xxx_irq_xxx(){ ...{ twd_handler(){ ...{ update_process_times(){ scheduler_tick(){ ...{ update_curr() //更新时间片计数 //检查是否被抢占(preemption)(取决于`preemption`算法) //如:进程时间片达到最大值,有优先级更高的进程 //判断是否需要重新调度,需要则设置TIF_NEED_RESCHED标志 check_preempt_tick() } } } } } } ret_to_user_from_irq (){ slow_work_pending (){ if TIF_NEED_RESCHED { do_work_pending(){ schedule() //进程调度 } } } } }
新进程创建的时候, 判断进程被抢占(preemption)(取决于
preemption
算法)......
linux
的线程
- 其实
linux
最初是不支持线程的, 好像unix
标准中就没有线程, 线程是在Posix
标准规定的(貌似,记不太得),linux
中的线程其实就是进程(轻量级进程), 线程的概念最初来源于windows
, 这里不作深究。
为什么多线程阻塞io
服务器模型会频繁触发进程调度
- 每次
io
操作, 进程变为睡眠状态, 触发进程调度。 周期性的时钟中断处理函数中, 会判断进程是否被抢占(preemption), 这取决于
preemption
算法(个人推测 4 核 40个进程 肯定 比 4 核 16个进程触发的次数多)。虽然使用了线程池, 但是还是免不了创建新线程, 可能会触发进程调度。
- 另外需要注意的一点是, 多线程阻塞
io
服务器模型不仅会频繁触发进程调度, 而且每次调度的进程数量(虽然有一部分进程处于TASK_UNINTERRUPTIBLE
,TASK_INTERRUPTIBLE
状态不需要被调度)要比事件驱动服务器模型的进程数量要多很多, 那么调度的时间是否也会变长, 当然这取决于进程调度算法,这里不作深究。
内存占用
- 一个线程至少要占用固定大小的内存作为函数栈, 还有一些内核结构体, 相对来说事件驱动模型更节省内存占用
Golang
- 用户态线程(goroutine
)调度
问题:
goroutine
是如何进行调度的呢?首先
linux
进程调度执行时机:- 从系统调用(
syscall
)返回用户态时 - 从中断返回用户态时
- 从系统调用(
显然
golang
程序不可能基于时钟中断, 系统调用, 那么go语言在什么时候进行进程调度呢?个人认为, go语言的所有
io
操作在底层实现都采用多路IO
多路转接,而并非 阻塞IO
(因为这会触发系统进程调度), 当goroutine
进行io
操作,channel
读写时(可能还有其他操作)时, 触发goroutine
调度。所以
goroutinue
没有强制的调度方式, 此时拿 多路IO复用事件驱动的服务器模型 做一下对比, 神似, 事件分发 换成 用户态线程调度,goroutinue
没有强制的调度方式, 在一些文章中也有体现:P与M一般也是一一对应的。他们关系是: P管理着一组G挂载在M上运行。当一个G长久阻塞在一个M上时,runtime会新建一个M,阻塞G所在的P会把其他的G 挂载在新建的M上。当旧的G阻塞完成或者认为其已经死掉时 回收旧的M。
个人结论:
- go语言的服务器模型是基于多路
io
复用以及用户态线程(goroutine
)调度的高并发服务器模型。 - 用户态线程调度比系统进程调度好在哪里呢, 我觉得是非强制的调度方式, 操作系统时钟中断的抢占调度算法是为了尽量公平的分配
cpu
资源, 而非强制的调度方式, 不在乎公平, 更多在乎性能, 使cpu
占用更多的跑在功能代码上, 提高并发。 - 我认为, 这种用户态的线程调度在性能上还是比不过事件驱动, 是两种服务器模型的折中, 提高了性能,降低了开发难度, 非常复杂的设计, 不愧为 为编写高性能高并发服务器设计的语言。
- go语言的服务器模型是基于多路
内容参考
有疑问加站长微信联系(非本文作者))

看到很多文章证明了自己的猜想, 分享一下: golang net包里的异步IO实现原理分析: http://blog.sina.com.cn/s/blog_630c58cb010188id.html golang 如何将多路复异步io转变成阻塞io的: https://blog.csdn.net/d_guco/article/details/75150696
真正实现事件驱动的也只有Windows的iocp而已,liunx的epoll并不是。