多路IO复用事件驱动的服务器模型比阻塞IO多线程服务器模型高在哪?

loop_0 · · 714 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

* 本文从两点进行分析: * 线程调度 * 内存占用 * 文章很多内容都是仅凭个人认知推测, 并未进行实时考证, 不过个人感觉应该有参考价值(不然也不会写了) ## 线程调度 ### `linux`进程调度的时机 * 进程不再处于`TASK_RUNNING`状态 * 如: 由于等待IO而阻塞(`TASK_UNINTERRUPTIBLE`),或者因等待资源和特定事件而休眠(`TASK_INTERRUPTIBLE`),又或者被debug/trace设置为`TASK_STOPPED`/`TASK_TRACED`状态。 * 周期性的时钟中断处理函数中, 判断进程被抢占(preemption)(取决于`preemption`算法) ```c 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`占用更多的跑在功能代码上, 提高并发。 * 我认为, 这种用户态的线程调度在性能上还是比不过事件驱动, 是两种服务器模型的折中, 提高了性能,降低了开发难度, 非常复杂的设计, 不愧为 为编写高性能高并发服务器设计的语言。 ## 内容参考 * [Linux进程调度浅析](https://blog.csdn.net/zhoutaopower/article/details/86290196) * https://blog.csdn.net/zhoutaopower/article/details/86290196 * [Go语言基础之并发](https://www.liwenzhou.com/posts/Go/14_concurrence/) * https://www.liwenzhou.com/posts/Go/14_concurrence

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

714 次点击  ∙  1 赞  
加入收藏 微博
2 回复  |  直到 2020-06-04 09:59:45
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传