文章17
标签2
分类9

有了多线程,为什么还要有协程?

进程

什么是进程

 进程,描述的就是程序的执行过程,是运行着的程序的代表。

进程的调度

 内核把 CPU 的执行时间切分成许多时间片,每个时间片再分发给不同的进程,通常,每个进程需要多个时间片才能完成一个请求。

进程的缺点

 在操作系统中,每个进程的内存空间都是独立的,这样用多进程实现并发就有两个缺点:一是内核的管理成本高,二是无法简单地通过内存同步数据,很不方便。

线程

什么是线程

 线程,是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程总是在进程之内的。一个进程至少会包含一个线程。

线程的调度

 线程被称为轻量级进程。CPU 在线程之间快速切换,制造了线程并行运行的假象。

 由于各个线程都可以访问进程地址空间的每一个内存地址,所以一个线程可以读、写,甚至清除另一个线程的堆栈。也就是说,线程之间是没有保护的。但要注意的是,每个线程都有自己的堆栈、程序计数器、寄存器等信息,这些不是共享的。

协程

什么是协程

 协程就是用户态的线程。通常创建协程时,会从进程的堆中分配一段内存作为协程的栈。线程的栈有 8MB,而协程栈的大小通常只有几十 KB。而且,C 库内存池也不会为协程预分配内存,它感知不到协程的存在。这样,更低的内存占用空间高并发提供了保证,毕竟十万并发请求,就意味着 10 万个协程。

协程的调度

 每个协程有独立的栈,而栈既保留了变量的值,也保留了函数的调用关系、参数和返回值,CPU 中的栈寄存器 SP 指向了当前协程的栈,而指令寄存器 IP 保存着下一条要执行的指令地址。因此,从协程 1 切换到协程 2 时,首先要把 SP、IP 寄存器的值为线程 1 保存下来,再从内存中找出协程 2 上一次切换前保存好的寄存器值,写入 CPU 的寄存器,这样就完成了协程切换。(Swoole4 实现原理相似。)

 在 GO 语言中,语言的运行时系统会帮助我们自动地创建和销毁系统级的线程。这里的系统级线程指的就是我们刚刚说过的操作系统提供的线程。而对应的用户级协程指的是架设在系统级线程之上的,由我们编写的程序完全控制的代码执行流程。用户级协程的创建、销毁、调度、状态变更以及其中的代码和数据都完全需要我们的程序自己去实现和处理。这带来了很多优势,比如,因为它们的创建和销毁并不用通过操作系统去做,所以速度会很快,又比如,由于不用等着操作系统去调度它们的运行,所以往往会很容易控制并且可以很灵活。

协程为什么存在

  • 节省 CPU,避免系统内核级的线程频繁切换,造成的 CPU 资源浪费。好钢用在刀刃上。而协程是用户态的线程,用户可以自行控制协程的创建与销毁,极大程度避免了系统级线程上下文切换造成的资源浪费。
  • 节约内存,在 64 位的 Linux 中,一个线程需要分配 8MB 栈内存和 64MB 堆内存,系统内存的制约导致我们无法开启更多线程实现高并发。而在协程编程模式下,可以轻松有十几万协程,这是线程无法比拟的。
  • 稳定性,前面提到线程之间通过内存来共享数据,这也导致了一个问题,任何一个线程出错时,进程中的所有线程都会跟着一起崩溃。
  • 开发效率,使用协程在开发程序之中,可以很方便的将一些耗时的 IO 操作异步化,例如写文件、耗时 IO 请求等。
本文作者:admin
本文链接:https://banned.top/archives/20/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可

0 评论

'