在传统的blockIO中,一个TCP连接的可读事件与用户的实际读取操作是糅合在一起的。用户想要读取数据只需要调用read系统调用,之后当前线程会阻塞在这里直到当前连接的读缓冲区有数据可读,此时操作系统会调度让此线程退出阻塞状态继续执行,在用户态我们就可以实现读取数据的操作了。

此时的读取操作都是阻塞的,不过因为用户直接用一个线程来实现一个连接的读写,当前线程的阻塞并不会对其它的连接产生影响,所以阻塞也无所谓。

后来因为C10k问题,也就是如何让一个操作系统能同时维护10k个连接的问题的提出,传统的blockIO模型已经不能符合人们此时的需求。因为在传统的IO模型中,TCP连接的可读可写事件以及读写操作本身都被实现在READ和WRITE系统调用中,而读写事件本身会阻塞当前线程,这也就导致了我们必须要给每一个读写操作分配一个单独的线程。

因为传统IO需要一个连接对应着一个线程,所以当连接数过多时线程数也很多,现代操作系统在线程过多时运行效率会明显降低,这主要是因为两个原因

  1. 操作系统需要为每一个线程存储一些meta信息(包括线程的当前上下文状态等等),当线程数过多时,会对内存造成一定的压力。
  2. 操作系统的线程切换操作是十分耗费系统资源的,当线程数过多时,线程切换的频率大大增加,线程切换次数变多会导致整个系统的吞吐量降低,因而会影响用户程序的执行效率。

现代操作系统使用多路复用和非阻塞来解决C10K问题,它们的核心在于细化了对连接的管理方式。以前我们管理连接只能使用READ和WRITE,然后无脑开个线程,让操作系统来帮助我们管理连接的可读可写事件。为了解决这些问题,我们开始需要自己管理连接的读写事件。

首先我们把连接的操作进行拆分,不再像以前那样READ和WRITE一把梭,而是把 可读可写读写操作本身 进行拆分。我们之前说过,我们之所以要给每一个连接创建一个线程,是为了能让操作系统帮助我们管理每一个连接的读写事件。

多路复用

我们把一个连接的可读可写事件剥离出来,使用单独的线程来对其进行管理,这里的关键点在于此线程不仅可以管理一个线程的可读可写事件,事实上这个线程中我们可以管理多个连接的可读可写事件,这个线程中实现的操作就叫 多路复用 ,多路复用需要操作系统提供相应的syscall才可以使用。

有了多路复用器,连接与线程之间的紧密联系被拆开。不再需要太多的线程,我们可以在仅一个线程中就维护着数百万个连接的读写事件,C10k问题被解决。

非阻塞

多路复用解决的是维护大量TCP连接的状态以及它们的可读可写事件的问题,这是我们在连接的可读可写事件上进行的优化,接下来我们需要对连接数据的读写操作本身进行优化。

一般来说,在从多路复用器得到了一个连接可读或者可写的讯息之后,我们就需要对这个连接进行读写操作了。因为多路复用器所在的线程可能会阻塞,所以我们一般会把这些连接的读写操作放到新的线程中。因为读写操作本身也可能导致线程阻塞(例如读取数据的数量还不满足要求),所以此时我们仍然需要为每一个连接的读写操作开辟新的线程(也可以使用线程池),这在读写连接较少的情况下没什么问题,但是在有大量连接都需要进行读写操作时仍然会产生大量的线程,降低系统吞吐量。

解决办法是使用非阻塞IO,即一旦当前连接读缓冲区中的数据已被读完或当前连接的写缓冲区中的数据已满,则READ或WRITE系统调用立即返回,而不是阻塞住当前线程。有了非阻塞IO,我们就可以在一个线程中进行多个连接的读写操作而不用担心某一个连接会导致当前线程阻塞,这样我们就能降低读写操作所需要的线程的数量了。

传统IO多路复用非阻塞读写事件被绑定在读写操作上,读写操作本身是阻塞的把读写事件剥离出读写操作本身,单个线程可以管理数百万个连接的读写事件,读写操作本身还是阻塞的读写操作本身是非阻塞的,可以在少量线程中实现大量连接的读写操作

这三种类型的IO模型的使用情况如下图:

协程

协程是用户态层面的代码执行管理单元,可以类比操作系统的线程。

类型线程协程被调度时meta数据的存储区域内核态内存空间用户态内存空间切换操作需要调用操作系统内核提供的syscall简单的现场保存和恢复即可调度机制现代操作系统都是抢占式的,由内核实现依赖当前协程主动让出(yield)CPU资源

由于多路复用与非阻塞的使用,导致单个连接的状态管理不再像BlockIO时那样的简单,而且因为线程不会阻塞在读写操作、尤其是读操作上,所以此时我们一般使用回调函数的方式来实现读操作。简单来说就是在读取时,如果已经读取的数据还不满足需求,程序就暂时把这些数据读取并保存在用户态的内存中,待数据读取满足要求之后就调用回调函数,通过异步的方式把数据交给相应的处理函数。

异步的问题在于不便于程序员的理解,人类更加习惯于同步的操作行为,异步的操作总是会显得晦涩而又难以理解,这会提升代码的复杂度。

我们可以使用协程对多路复用和非阻塞进行改造来实现同步的IO操作。首先我们在协程中调用我们自己实现的方法 READ0 ,该方法为阻塞方法,此时该协程被阻塞。在语言内部我们使用多路复用和非阻塞来管理连接和数据,一旦数据满足了要求,我们再次调度到该协程,此时 READ0 方法返回,在用户看来整个 READ0 方法就是同步阻塞的,非常易于程序员的使用。

此外,由于协程的数据都存储在用户态的内存空间且不需要通过syscall即可以调度,所以协程的调度相较于线程是非常轻量级的,事实上在go语言中我们可以开数十万个协程而不会有性能问题,而同样的机器上运行数千个线程就已经很吃力了。

协程和线程不存在相互替代的关系,它们都是对一个指定的逻辑流的抽象,它们之间是互补的关系。协程和线程的发展历史大致如下:

  1. 早期一台计算机上面只能执行一个程序
  2. 一台机器上只执行一个程序太浪费CPU资源了,我们可以写一个控制程序,当某个程序执行IO时就让出CPU资源交给另一个程序执行,这就是协程的思想
  3. 在多任务操作系统中,为了避免某个程序一直霸占CPU资源,抢占式的操作系统被发明,由操作系统内核对CPU的资源进行管理和分配(事实上不仅仅是CPU,现代操作系统实现了对计算机所有的硬件资源的高效的管理)
  4. 多核CPU被发明,我们可以直接在操作系统层面支持多核,面向程序员的线程模型无需改变
  5. 线程的切换需要内核的帮助,比较耗费系统资源。为了避免大量的使用线程,我们可以在单个进程中模拟早期的调度程序的行为,从而实现多个逻辑流的执行,这就是协程

在操作系统层面,线程实现了抢占式多任务处理以及对多核CPU的支持;在用户层面,协程提供了统一的逻辑流的抽象,并向上提供编程模型。协程和线程之间是互补的关系,它们本质上都只是对一些的状态的维护。

多路复用器_多路复用、非阻塞、线程与协程相关推荐

  1. python协程是什么_在python中线程和协程的区别是什么

    在python中线程和协程的区别:1.一个线程可以拥有多个协程,这样在python中就能使用多核CPU:2.线程是同步机制,而协程是异步:3. 协程能保留上一次调用时的状态,每次过程重入时,就相当于进 ...

  2. php携程 线程,244,android线程与协程以及携程的使用方法

    1.线程就是线程, 2.协程本质就是一种线程框架,仅仅是针对Java中的Thread做了一次更友好的封装.让我们更方便的使用Java中的线程才是Kotlin-JVM中协程的真正目的. 本质上和Hand ...

  3. 操作系统的线程和进程的区别_进程,线程,协程,有何区别?

    进程 ​ cpu是计算机的核心,主要处理计算机的计算任务.操作系统是计算机的管理员,它负责任务的调度,资源的管理和分配,统一管理计算机的硬件资源.应用程序则是具有某种功能的程序,应用程序运行于操作系统 ...

  4. Python进阶(5)_进程与线程之协程、I/O模型

    三.协程 3.1协程概念 协程:又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存 ...

  5. python进程线程协程区别_进程和线程、协程的区别

    现在多进程多线程已经是老生常谈了,协程也在最近几年流行起来.python中有协程库gevent,py web框架tornado中也用了gevent封装好的协程.本文主要介绍进程.线程和协程三者之间的区 ...

  6. python线程协程进程的区别_进程和线程、协程的区别

    现在多进程多线程已经是老生常谈了,协程也在最近几年流行起来.python中有协程库gevent,py web框架tornado中也用了gevent封装好的协程.本文主要介绍进程.线程和协程三者之间的区 ...

  7. python 多线程和协程结合_一文讲透 “进程、线程、协程”

    本文从操作系统原理出发结合代码实践讲解了以下内容: 什么是进程,线程和协程? 它们之间的关系是什么? 为什么说Python中的多线程是伪多线程? 不同的应用场景该如何选择技术方案? ... 什么是进程 ...

  8. 并发集合(二)使用非阻塞线程安全的列表

    使用非阻塞线程安全的列表 列表(list)是最基本的集合.一个列表有不确定的元素数量,并且你可以添加.读取和删除任意位置上的元素.并发列表允许不同的线程在同一时刻对列表的元素进行添加或删除,而不会产生 ...

  9. 非阻塞线程安全列表——ConcurrentLinkedDeque应用举例

    转载自  非阻塞线程安全列表--ConcurrentLinkedDeque应用举例 在java中,最常用的数据结构可能是列表.有数目不详的元素列表,你可以添加.阅读.或删除任何位置的元素.此外,并发列 ...

  10. input 输入事件_输入超时为例学习 Python 的线程和协程

    需求:做一个程序等待用户输入,3秒内输入则会 echo 这个输入并立即退出.3秒内没输入则自动退出. 实现方法: 1. 线程(错误示范) import 首先启动两个线程,并把等待输入的 get_inp ...

最新文章

  1. 2018.11.12
  2. 报错解决:cp: error while loading shared libraries: libc.so.6: cannot open shared object file: No such fi
  3. 【读】这一次,让我们再深入一点 - UDP协议
  4. java基础 通过继承Thread类和实现Runnable接口创建线程
  5. 理论基础 —— 二叉树 —— 树、森林、二叉树的转换
  6. 程序员如何用编程套路追到女朋友的?
  7. Python的BoundedSemaphore对象和Pool对象实例
  8. 客户端DDK编译环境配置说明
  9. java ee图书管理系统_基于jsp的图书馆管理系统-JavaEE实现图书馆管理系统 - java项目源码...
  10. HTML5系列代码:注册商标reg_和版权商标copy
  11. mysql top percent_SQL Server -- TOP子句/TOP Percent,IN 操作符
  12. winform datagridview 没有出现垂直滚动条 上下_木门安装中出现问题如何解决?
  13. Linux ping 测试IP地址与 telnet 测试IP端口
  14. c语言 switch 单引号,在switch语句中表示单引号的错误
  15. 【border相关】【P3426】 [POI2005]SZA-Template
  16. Android Studio中对res、AndroidManifest、buil.gradle文件夹的讲解
  17. 【Nginx】配置中 resolver 指令的使用
  18. 云图数字iOS客户端
  19. 建立良好体验度的Web注册系统
  20. Pycharm 远程debug项目配置

热门文章

  1. 29.yii2 RBAC
  2. 2.kafka 安装
  3. 63.magento 后台重置密码
  4. 17. jQuery - css() 方法
  5. 15. Window clearTimeout() 方法
  6. css3中transform-style的用法
  7. Django2.1.1与xadmin0.6.0遇到的坑
  8. Burpsuit结合SQLMapAPI产生的批量注入插件
  9. ubuntu 环境变量配置
  10. 《大象UML》看书笔记2: