什么是协程?

文章目录

  • 一、进程、线程、协程的概念理解:
  • 二、为什么需要协程?
  • 三、协程的使用场景?
  • 四、协程、线程、进程关于上下文切换的比较?
  • 五、协程的类别?
  • 六、协程的注意事项?


一、进程、线程、协程的概念理解:

进程:是程序动态运行的一个过程,是操作系统资源分配和独立运行的基本单位。其有自己独立的内存空间,不同的进程之间通过各种相对应的通信方式进行通信。但又因为进程拥有独立的内存空间,所以它的上下文切换(栈,寄存器,页表,文件句柄等)开销就比较大,但是相对来说进程的运行就比较安全稳定。
线程:又可以叫做轻量级进程,是CPU任务调度和系统执行的最小单位。线程从属于进程,是程序的实际执行者。一个进程至少包含一个主线程,也可以包含更多的子线程。多个线程可以共享所属的进程资源,同时线程也有自己独立的专属资源和栈空间。线程间的通信主要通过共享内存,上下文切换很快,资源开销比较小,但相比进程而言不够稳定且容易丢失数据。
协程:
协程是属于线程的。协程程序是在线程里面跑的,因此协程又称微线程和纤程等;
协程没有线程的上下文切换消耗。协程的调度切换是用户(程序员)手动切换的,因此更加灵活,因此又叫用户空间线程.
原子操作性。由于协程是用户调度的,所以不会出现执行一半的代码片段被强制中断了,因此无需原子操作锁。

什么是协程?_第1张图片

二、为什么需要协程?

简单来说,线程粒度还不够细。举个例子,在网络服务中,调用read函数读取数据,如果socket缓冲区没有数据,当前线程就会阻塞一直到缓冲区可读才行。注意,整个线程会被阻塞,而并发性能自然会受到影响。如果能把线程更细粒度区分为很多子任务,线程在多个子任务之间交替执行。比如在子任务A里面调用 read 函数,如果socket不可读,那么子任务A阻塞,让出执行权,线程转而去执行其他的子任务。 当可读条件满足后,线程又唤醒子任务A,从上次read阻塞的地方恢复继续执行。可以看到,线程并没有阻塞,而是转而去执行其他任务。这对并发就进一步提高了。另外,这里子任务简单来说就是一个函数罢了,要封装这么一个子任务也很简单,把当前函数的栈空间、寄存器状态保存下来即可。而这个子任务,其实就是协程的概念。由于它只用一些寄存器状态就可以描述,所以其实协程占用的资源非常少,要实现上万的协程是非常容易的。然而如果是上万个线程,操作系统就要骂娘了。

三、协程的使用场景?

协程的主要应用场景是 IO 密集型任务,总结几个常见的使用场景:网络请求,比如爬虫,大量使用 aiohttp、文件读取, aiofile、web 框架, aiohttp, fastapi、数据库查询,

四、协程、线程、进程关于上下文切换的比较?

(1)进程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户是无感知的。进程的切换内容包括页全局目录、内核栈、硬件上下文,切换内容保存在内存中。进程切换过程是由“用户态到内核态到用户态”的方式,切换效率低。

(2)线程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户无感知。线程的切换内容包括内核栈和硬件上下文。线程切换内容保存在内核栈中。线程切换过程是由“用户态到内核态到用户态”, 切换效率中等。协程的切换者是用户(编程者或应用程序),切换时机是用户自己的程序所决定的。

(3)协程的切换内容是硬件上下文,切换内存保存在用户自己的变量(用户栈或堆)中。协程的切换过程只有用户态,即没有陷入内核态,因此切换效率高。

五、协程的类别?

协程的实现不只有一种,很多活跃的语言如 Python、Java、Golang等都是支持协程的;

尽管这些协程可能名称不同,甚至用法也不同,但它们都可以被划分为两大类:

有栈(stackful)协程,这类协程的实现类似于内核态线程的实现,不同协程间切换还是要切换对应的栈上下文,只是不用陷入内核而已;例如:goroutine、libco;无栈(stackless)协程,无栈协程的上下文都会放到公共内存中,在协程切换时使用状态机来切换,而不用切换对应的上下文(因为都已经在堆中了),因此相比有栈协程要轻量许多;例如:C++20、Rust、JavaScript 中的协程。

这里所谓的有栈、无栈:
并不是说这个协程运行的时候有没有栈,而是说协程之间是否存在调用栈(Callback Stack);

同时,根据协程之间是否有明显的调用关系,我们又可以把协程分为:

非对称协程:协程之间有明显的调用关系;对称协程:协程之间无明显的调用关系。

六、协程的注意事项?

协程对计算密集型的任务也没有太大的好处,计算密集型的任务本身不需要大量的线程切换,因此协程的作用也十分有限,反而还增加了协程切换的开销。

你可能感兴趣的:(linux,运维,服务器)