118、Python并发编程:一文搞懂协程的概念及Python原生协程的使用

南宫理的日志录 2024-12-12 09:19:32
引言

在前面通过多线程、多进程进行并发编程时,已经能够覆盖大部分的并发场景。但是,在代码编写方面以及实际的性能提升方面,还是存在一些问题的(即便抛开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的原生协程的使用。

感谢您的拨冗阅读,希望对您有所帮助!

0 阅读:4

南宫理的日志录

简介:深耕IT科技,探索技术与人文的交集