在前面通过多线程、多进程进行并发编程时,已经能够覆盖大部分的并发场景。但是,在代码编写方面以及实际的性能提升方面,还是存在一些问题的(即便抛开GIL的因素,有些场景的并发似乎反而不及单线程)。
由于存在的一些问题,自然而然想到了有没有解决现存问题的方案,毕竟偷懒、贪心是大部分人的常态,既要、又要、还要,始终是成年人的最佳选择。本文的主角“协程”,就是这种既要、又要、还要的解决方案。
本文的内容主要有:
1、现存问题及协程的引入
2、进程、线程、协程的比较
3、Python协程的实现方案
4、使用async/await编写原生协程
现存问题及协程的引入基于多线程、多进程实现的并发编程,能够覆盖大部分的场景需求,但是,还是存在一些问题的:
1、线程、进程创建及上下文切换,会导致较大的资源、性能的开销,比如极端情况下的C10M问题:如何利用8核CPU、64G内存、10gbps的网络环境中实现1000万的并发连接。
2、线程、进程的并发编程,会涉及到线程间同步等问题,需要进行额外的同步机制编写,比如Lock。
由于这些问题的存在,我们很自然地会想到:
1、能否进一步降低任务切换的开销,没有线程创建、甚至进程创建和切换的开销?
2、能否采用我们以往编写同步代码的习惯性做法,实现异步的开发?
进程的创建需要申请更多的资源,是比较重的方案,所以进一步发展出了更轻量级的线程方案。如果还要进一步降低开销,自然而然想到能否在单线程内做文章,我们在一个线程内实现多个任务的自行切换。
任务通常是由函数来实现的,要能够实现单线程内进行多个函数的调度切换,首先我们需要的是能够更加灵活控制的函数,比如至少能够做到像线程切换一样,可以挂起/暂停,可以恢复执行等。
这个比线程更细粒度的解决方案,就是协程(Coroutine)。
所谓协程,是一种用于构建并发程序的计算结构。与传统的线程和进程相比,协程是一种更加轻量级的并发编程方式,它允许在单个线程中进行多任务处理。协程通过在函数执行期间的“挂起”和“恢复”来实现任务的切换,从而实现并发。
这个概念有点官方,其实,另一种更加直观的说法是:协程就是可以暂停的函数!
进程、线程、协程的比较从组成结构上可以看出:
1、进程是操作系统进行资源分配的基本单位,这些资源通常是存储在内存中的程序代码、数据、进程打开的文件等。
2、线程是操作系统进行调度(本质上是CPU资源分配)的基本单位,线程的执行是相互独立的,线程执行相关的上下文环境,包括调用栈、通用寄存器、指令计数器等。所谓的上下文切换,其实就是将挂起的线程的这些内容保存起来,将调度执行的线程的上下文内容进行恢复,然后进行线程指令序列的执行。
3、协程是一个进程内可以挂起、恢复的不同任务的函数。
简单比较一下三者:
Python协程的实现方案Python的协程方案,其实经历了比较场的一段历程,主要分为3个阶段:
1、可暂停的函数,很自然想到了生成器,基于生成器的yield/send语法变形而为协程,但是编写起来比较麻烦。
2、引入装饰器@asyncio.coroutine 和 yield from语法。
3、Python 3.5之后,引入async/awit关键字,进行更加自然的原生协程开发。
当然,现在进行协程开发,推荐的都是使用async/await关键字进行原生协程开发,而不在使用生成器进行开发。因为代码编写起来更加简洁、直观。
但是,如果想要深入了解协程的实现机制,还是可以通过研究生成器相关的yield、send、throw、close、yield from等实现机制。
生成器相关的内容,前面的文章中已经有所涉及,感兴趣的同学可以自行查阅历史文章。
使用async/await编写原生协程接下来,通过代码简单演示一些基于async/await进行原生协程的开发,直接来看代码:
执行结果:
对代码及执行结果做个简单的说明:
1、代码中使用asyncio.sleep(1)模拟函数需要执行IO操作,应当挂起、让渡CPU资源。注意,不能使用time.sleep(),这个是使整个线程阻塞。
2、可以看到两个任务Task1执行到IO任务时,会让渡CPU,切换到Task2的执行,两者是在交互执行。真正实现了线程内的多个任务的切换。
3、代码的编写上,除了async/await关键字的部分,基本上是按照我们进行同步编程中代码的编写方式,很自然地就实现了基于协程的异步编程。所以,协程方案下的代码编写更加简洁、清晰。
需要特别说明的是,关于协程的应用场景,需要结合到后续异步io的内容,才会有个更加深入的掌握。这里,为了保证并发编程内容上的完整性,所以,提前介绍了。
总结本文通过对多线程、多进程方案中的并发编程的问题的描述,引出了协程的概念,以及协程主要解决的问题。简单比较了进程、线程、协程各自的特点,并描述了Python中协程的实现历程。最后,通过代码演示了基于async/await的原生协程的使用。
感谢您的拨冗阅读,希望对您有所帮助!