首页 协程的介绍
文章
取消

协程的介绍

前言

协程的引入在程序的调度中有着极为深远的意义,众所周知,为了实现了程序的并行,从进程开始,逐步引入线程,而随着业务场景的需要,在线程的基础上,又引入了协程这一概念,协程随着go语言的广泛使用,不断的被各大编程语言引入其中;

协程是一种轻量级的线程,可以在一个线程内执行异步非阻塞的任务,而不会阻塞整个线程。它们在编写高效的并发和异步代码时非常有用。以下是一些协程的应用示例:

  1. Web服务器:协程可以用于编写高性能的Web服务器,每个请求可以由一个协程处理,而不需要为每个请求创建一个新线程。
  2. 爬虫和网络爬取:在网络爬虫中,协程可以用于异步下载和处理网页,提高爬取效率。
  3. 数据库访问:协程可以用于异步数据库访问,提供非阻塞的数据库查询和操作。
  4. 游戏开发:在游戏开发中,协程可以用于处理角色行为、动画和事件处理。
  5. 图像处理:协程可以用于异步图像处理,如图像压缩、裁剪和滤镜应用。
  6. 事件循环:协程可以用于构建事件循环,用于处理异步事件,例如用户界面事件、网络套接字事件等。
  7. 任务调度:协程可以用于任务调度,例如在并行计算中将任务分发到多个工作线程或处理器上。
  8. 流式处理:协程可用于实现流式处理,例如在数据流处理中,异步处理大量数据。
  9. 多协程编程框架:一些编程框架,如PythonasyncioGogoroutines,利用协程来简化异步编程。
  10. 批量文件处理: 协程可以用于异步处理大量文件,例如文件复制、转码或解析。

协程的特点

协程主要在一个单一的线程内支持多个同时执行的任务,而不需要创建多个操作系统级线程,协程可以暂停执行,并在稍后恢复执行,而不会阻塞整个线程。这种特性使得协程非常适用于编写高效的并发和异步代码。

线程和协程的关系既可以一对多,也可以多对多:

  • 一个线程可以创建、启动和管理多个协程,这些协程可以在同一个线程内交替执行,共享线程的资源,但是它们是协作式的,需要在适当的时机主动让出CPU控制权。
  • 一个协程在多个线程之间切换:有些协程框架支持将一个协程在多个线程之间切换执行,这可以用于实现并发性,允许协程利用多核处理器的性能。
    • 还没看到过实际的应用例子;

通过上面的叙述,我们貌似看到了协程既然这么多可取之处,为何不彻底替代线程呢?根本原因就在于这两者并不是一个维度上的东西,协程可以说是一个独立于线程的功能,它是在线程的基础上,针对某些应用场景进一步发展出来的功能。我们知道,线程在多核的环境下是能做到真正意义上的并行执行的,注意,是并行,不是并发,而协程是为并发而生的。

并行并发

结合一个例子来理解协程的特点:

假设需要执行两个互不干扰的sql查询,为了减少等待时间,常规的操作肯定主线程执行sqlA的同时另起一个线程执行sqlB,使两个sql并行执行。然而执行两个sql的线程大多数时间只是在等待数据库服务器的响应,线程只是处于阻塞等待状态,而不是疯狂运转,而线程的创建、切换又很消耗系统资源,显然这很浪费。

这个时候就该协程大展身手了,你可以在主线程中创建一个协程用于执行sqlA,然后再在主线程中执行sqlB,协程和线程一样,不会阻塞它的主线程,所以sqlA得到结果后,你可以通过语言的api去看看在协程中的sql执行完毕了没有,如果没有则等待,如果执行完毕了就拿结果,和线程操作几乎一摸一样。

至于sqlA和sqlB是否真正在并行执行根本无所谓。为什么呢? 我们假设执行一个sql需要三步,提交sql、等待、获得结果,其中第一步和第三步极省时,只要1毫秒一步,而第二步却要1000毫秒,那么使用并行的多线程执行两个sql,你只要花掉1002毫秒,而使用并发的协程你要花掉1004毫秒,但是线程比协程多消耗一个线程的资源,请问你会为了这2毫秒而选择多线程吗,显然不可能,创建线程的开销都要大于节省下来的时间,这就是协程存在的理由。

从这个例子我们可以看到的是,协程在它的能力范围内尽可能的减少了资源的切换,它不需要向操作系统汇报它的工作,它的根源就在于线程,只需要向线程负责,因此在那种需要线程频繁切换的业务场景中,协程的优势一览无余;

服务器端开发中,大多数时候都是要花大量等待时间的场景,也就是所谓的IO密集场景,协程极为适合这种场景,而go又主打协程,直接从语法层面支持,因此go在后端层面得到了广泛的运用;

这时候有人会提出反问,网络编程不是存在IO复用吗?但是IO复用与协程要处理的就不是一个问题:

  • I/O复用处理的是数据间的传输效率问题;
  • 协程处理的是线程间状态频繁切换的调度问题;

正如上面例子所示,协程本质上是面向并发,面向并发这一点会使得在执行时间上不一定有线程的效率,但是线程的频繁调度同样是需要时间,而协程省的就是这部分时间;

当然,IO复用也可以在线程和进程的切换和时间处理上起到很大作用,但是根本上就如上面所说,两者的目的不一样;

有这么一个比较形象的例子,很贴切的说明了协程的特点:协程A/B/C就是在用户态实现的小型调度器。由用户态的进程/线程代码决定在何时执行协程A/B/C,直接将协程理解为用户态状态机,比如协程A就是状态a,协程B就是状态b,当协程A发生IO时就切到协程B去执行,协程的切换相对于线程而言是一件比较轻松的事情;

本文由作者按照 CC BY 4.0 进行授权

设计模式之单例模式

2023-09-08之度小满笔试