我认为的协程它真的是协程吗?

200 阅读7分钟

网友的讨论

image.png

首先什么是抢占式?

你会想到相关的关键词:

  • 中断
  • 线程上下文切换
  • 线程优先级

抢占式多任务处理算法的缺点

  1. 上下文切换开销大:抢占式调度需要频繁地进行上下文切换,即从一个任务切换到另一个任务,这会带来较大的时间和资源开销。
  2. 可靠性差:由于任务之间可以相互抢占,因此程序对共享资源的访问需要加锁,而锁的使用可能会引发死锁等问题,从而影响程序的可靠性。

那么非抢占式呢?

不会中断, 没有上下文切换开销, 一个任务可以一直执行到完毕才去切换到下一个任务???

要是这么简单讲完就好了

协作式多任务(Cooperative Multitasking)

协作(合作)式多任务处理,也被称为非抢占式多任务处理,是一种计算机多任务处理的方式,其中操作系统从不主动发起从一个运行中的进程切换到另一个进程的上下文切换。相反,为了同时运行多个应用程序,进程会定期或在空闲或逻辑阻塞时自愿放弃控制权。这种类型的多任务处理被称为协作式,因为所有程序必须相互合作以使调度方案正常工作。

在这种方案中,操作系统的进程调度器被称为协作式调度器,其角色仅限于启动进程并让它们自愿返回控制权给调度器

那么现在再回到这张图: image.png

现在黄色框框的部分看得懂了吧?

其实他在后续红色框框部分已经说了, 是协程使用者, 也就是程序员决定在哪里主动放弃协程控制权, 主动切换到另一个任务执行

核心: 程序员决定在哪里主动放弃协程控制权, 主动切换到另一个任务执行

协程的本质

那么维基百科上的协程是怎样的?(懒得看)

协同程序是一种组件,可以暂停执行和恢复,使得子程序可以协作执行多个任务。它们适合实现常见的程序组件,例如协作任务、异常、事件循环、迭代器、无限列表和管道。协同程序可以描述为“可以暂停执行的函数”。梅尔文·康威在1958年将“协同程序”一词引入到汇编程序中,协同程序的详细说明最早于1963年发表。

协同程序有两个基本特征:
1.本地数据在调用之间保持不变
2.执行可以暂停并在以后继续进行。

此外,协同程序还有三个特点:
1.控制转移机制
2.作为一级对象或受限制的构造(也就是说协程可以传递, 并且需要在协程作用域下才能访问)
3.有栈或无栈。完整协同程序支持一级协同且具有堆栈,可以是对称或非对称的,比非完整协同程序更具表达能力。虽然对称和非对称协同程序具有相同的表达能力,但非对称协同程序更适合基于例程的控制结构。

我认为的协程是?

协程是“由用户控制的带有自定义调度器的用户线程”。

有人可能会坚持认为协程比线程更强大,因为一个进程中可以创建更多的协程,而且每个协程所需的内存更少。但是,这并不矛盾于前面所说的“用户线程”概念。实际上,协程和线程都是用来支持并发编程的工具,它们各有优劣,应该根据特定的业务场景和需求来选择使用哪种方式。

协程的本质是线程要执行的任务

核心: 协程的本质是线程要执行的任务

虽然一个进程中只有有限的线程,但是可以根据需要创建大量的协程(任务)来满足业务需求,因为协程的执行依赖于线程。协程通过调度器利用单线程内部的上下文切换技术来实现并发,同时减少了线程上下文切换的频率。

协程和系统线程的本质区别在于调度器的不同。协程的调度器是用户自己实现的,不再依靠系统调度,而且协程的实现还需要重写IO库,才能让协程在多IO场景下发挥效果。此外,协程的优势之一是能够更加直白地编写异步代码,可以减少应用去创建更多的线程从而减少线程上下文切换。

你说的协程就是任务, 那么线程池不也可以实现协程了吗?

是的,虽然线程池和协程都可以用来实现并发处理任务的效果,但是在多IO场景下,协程通常伴随着相应的协程库。这些库通常实现了很多的异步IO API,这使得开发者们可以非常方便地使用非阻塞的IO操作。相比之下,如果你使用线程池来实现异步IO操作,就可能需要自己实现各种复杂的机制,整个实现过程会更加复杂而麻烦。

要知道, 协程没什么复杂的, 其之所以合适多IO场景, 是因为有人在背后默默付出(比如Reactor Proactor或者其他合适多IO的设计)

协程的优点

  1. 协程占用内存更小, 无栈协程通常只有几十字节的大小, 有栈协程通常只有几kb的大小
  2. 对协程的充分利用, 可以减少用户主动去创建更多的线程, 从而从整体上看减少了线程上下文切换
  3. 使用协程可以更加简单的编写异步代码
  4. 协程的出现通常伴随着特定的协程函数, 更加合适多IO操作

课外: 聊聊goroutine

goroutine的实现确实不同于传统意义上的协程。严格来说,它更像是用户线程而不是协程。这是因为它并没有像协程那样进行显式的挂起和恢复。它采用了一种称为M:N调度的方式,即将M个goroutine映射到N个OS线程上进行并发执行。

有人会说channel是go协程的挂起和恢复, 但我认为它更像一个关于协程的阻塞队列

讨论goroutine到底算不算协程, 其实我们可以从goroutine底层原理入手

GMP模式

当我们在使用Golang编写并发代码时,经常会听到有关G、M、P和队列的术语。这里简单解释一下它们的意思。

首先,每个G(goroutine)都可以在一个或多个操作系统的线程(M)上运行,并由处理器(P)进行调度管理。

在这个模式下,每个P都是由M提供"发动机"的, P可以在"发动机"坏掉的情况下换一个新的

P内部还有一个本地队列来存储等待执行的G。当P需要执行任务时,它会从其本地队列中获取一个G进行执行。如果本地队列为空,则P会尝试从全局队列中获取可用的G,以确保最大限度地利用系统资源。

当全局队列为空时,P可能会去抢夺其他P的本地队列中的部分G,以提高并发效率。这种偷窃机制能够有效减少无用的等待时间,提高并发性能和效率。

整个过程如下图:

image.png

通过了解GMP模式, 我们基本可以得出结论, goroutine 更像是用户线程池

但是goroutine比协程更加好用, 更加高效, 他是对多核心操作系统下协程的改造, 虽然它已面目全非