浅析Python的进程、线程与协程(下)

简介: 进程进程是指在系统中正在运行的一个应用程序,是CPU的最小工作单元。进程有就绪、运行、阻塞、创建和退出五种状态。其中,运行中的三种状态:就绪、运行、阻塞。创建和退出是描述产生和释放的状态。


Python协程

运行效率极高,协程的切换完全由程序控制,不像线程切换需要花费操作系统的开销,线程数量越多,协程的优势就越明显。

同时,在Python中,协程不需要多线程的锁机制,因为只有一个线程,也不存在变量冲突。

协程对于IO密集型任务非常适用,如果是CPU密集型任务,推荐多进程+协程的方式。对于多核CPU,利用多进程+协程的方式,能充分利用CPU,获得极高的性能。

Python协程的发展时间较长:

  • Python2.5 为生成器引用.send()、.throw()、.close()方法
  • Python3.3 为引入yield from,可以接收返回值,可以使用yield from定义协程
  • Python3.4 加入了asyncio模块
  • Python3.5 增加async、await关键字,在语法层面的提供支持
  • Python3.7 使用async def + await的方式定义协程
  • 此后asyncio模块更加完善和稳定,对底层的API进行的封装和扩展
  • Python将于3.10版本中移除以yield from的方式定义协程

示例代码如下:

1. Python2.x 实现协程

Python2.x对协程的支持是通过generator实现的。

在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。

但是Python的yield不但可以返回一个值,它还可以接收调用者发出的参数。

下面改用协程实现生产者消费者模式,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产, 效率极高:

def consumer():
    print("consumer----------")
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('[消费者]Consuming %s...' % n)
        r = '200 OK'
def producer(c):
    print("producer----------")
    # 启动生成器
    c.send(None)
    n = 0
    while n < 5:
        n = n + 1
        print('[生产者]Producing %s...' % n)
        r = c.send(n)
        print('[生产者]Consumer return: %s' % r)
    c.close()
if __name__ == '__main__':
    c = consumer()
    producer(c)
复制代码


代码流程说明:

consumer函数是一个generator,把一个consumer传入produce后:

  1. 首先调用c.send(None)启动生成器(在生成带有yield的  generator后 第一个迭代必须是__next__() __next__()  和 send(None) 的效果是相同的);
  2. 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;
  3. consumer通过yield拿到消息,处理,又通过yield把结果传回;
  4. produce拿到consumer处理的结果,继续生产下一条消息;
  5. produce决定不生产了,通过c.close()关闭consumer,整个过程结束。

运行结果:

producer----------
consumer----------
[生产者]Producing 1...
[消费者]Consuming 1...
[生产者]Consumer return: 200 OK
[生产者]Producing 2...
[消费者]Consuming 2...
[生产者]Consumer return: 200 OK
[生产者]Producing 3...
[消费者]Consuming 3...
[生产者]Consumer return: 200 OK
[生产者]Producing 4...
[消费者]Consuming 4...
[生产者]Consumer return: 200 OK
[生产者]Producing 5...
[消费者]Consuming 5...
[生产者]Consumer return: 200 OK
复制代码


send(msg)next()的区别

send可以传递参数给yield表达式,这时传递的参数会作为yield表达式的值,而yield的参数是返回给调用者的值。

换句话说,就是send可以强行修改上一个yield表达式的值。

比如函数中有一个yield赋值a = yield 5,第一次迭代到这里会返回5,a还没有赋值。

第二次迭代时,使用send(10),那么就是强行修改yield 5表达式的值为10,本来是5的,结果a = 10

send(msg)next()都有返回值,它们的返回值是当前迭代遇到yield时,yield后面表达式的值,其实就是当前迭代中yield后面的参数。

第一次调用send时必须是send(None),否则会报错,之所以为None是因为这时候还没有一个yield表达式可以用来赋值。

2. Python3.x 实现协程

要点

  1. 使用async def的形式定义
  2. 在协程中可以使用await关键字,注意,其后跟的是"可等待对象"(协程, 任务 和 Future)
  3. 协程不能直接执行,需要在asyncio.run()中执行,也可以跟在await后面
  4. asyncawait这两个关键字只能在协程中使用
import asyncio
async def foo(name):
   await asyncio.sleep(1)      # 这是一个不会阻塞的sleep,是一个协程
   print(f"name = {name}")
async def main():
   # 协程本身就是一个可等待对象
   await foo("lczmx")  # 执行协程
   print("done")
if __name__ == '__main__':
   # 使用asyncio.run运行
   asyncio.run(main())
复制代码


运行结果:

name = lczmx
done
复制代码


其中,asyncio.run(main, *, debug=False)方法就是对run_until_complete进行了封装:

loop = events.new_event_loop()

return loop.run_until_complete(main)

关于可等待对象说明

可等待对象(awaitable)是能在 await 表达式中使用的对象。可以是协程或是具有__await__() 方法的对象。

那么协程是如何成为可等待对象的呢?

  1. collections.abc.Awaitable类,这是为可等待对象提供的类,可被用于 await 表达式中。
class Awaitable(metaclass=ABCMeta):
   __slots__ = ()
   @abstractmethod
   def __await__(self):   # __await__方法必须返回一个 iterator
      yield
   @classmethod
   def __subclasshook__(cls, C):
      if cls is Awaitable:
         return _check_methods(C, "__await__")
      return NotImplemented
复制代码


  1. async def复合语句创建的函数,它返回的是一个Coroutine对象,而Coroutine继承Awaitable

3. Python3.x 使用协程进行并发操作

使用协程进行并发操作,在Python3.7以上的版本,使用asyncio.create_task(coro)方法,返回一个Task对象,Task类继承Future,在Python3.7以下版本中,使用asyncio.ensure_future(coro_or_future)

import asyncio
async def foo(char:str, count: int):
    for i in range(count):
        print(f"{char}-{i}")
        await asyncio.sleep(1)
async def main():
    task1 = asyncio.create_task(foo("A", 2))
    task2 = asyncio.create_task(foo("B", 3))
    task3 = asyncio.create_task(foo("C", 2))
    await task1
    await task2
    await task3
if __name__ == '__main__':
    asyncio.run(main())
复制代码


执行结果:

A-0
B-0
C-0
A-1
B-1
C-1
B-2
复制代码


总结

  1. 线程和协程推荐在IO密集型的任务(比如网络调用)中使用,而在CPU密集型的任务中,表现较差。
  2. 对于CPU密集型的任务,则需要多个进程,绕开GIL的限制,利用所有可用的CPU核心,提高效率。
  3. 在高并发下的最佳实践就是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
相关文章
|
1月前
|
安全 数据处理 开发者
Python中的多线程编程:从入门到精通
本文将深入探讨Python中的多线程编程,包括其基本原理、应用场景、实现方法以及常见问题和解决方案。通过本文的学习,读者将对Python多线程编程有一个全面的认识,能够在实际项目中灵活运用。
|
13天前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
18天前
|
Linux 调度 C语言
深入理解操作系统:进程和线程的管理
【10月更文挑战第32天】本文旨在通过浅显易懂的语言和实际代码示例,带领读者探索操作系统中进程与线程的奥秘。我们将从基础知识出发,逐步深入到它们在操作系统中的实现和管理机制,最终通过实践加深对这一核心概念的理解。无论你是编程新手还是希望复习相关知识的资深开发者,这篇文章都将为你提供有价值的见解。
|
15天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
24 1
|
20天前
深入理解操作系统:进程与线程的管理
【10月更文挑战第30天】操作系统是计算机系统的核心,它负责管理计算机硬件资源,为应用程序提供基础服务。本文将深入探讨操作系统中进程和线程的概念、区别以及它们在资源管理中的作用。通过本文的学习,读者将能够更好地理解操作系统的工作原理,并掌握进程和线程的管理技巧。
36 2
|
22天前
|
调度 Python
深入浅出操作系统:进程与线程的奥秘
【10月更文挑战第28天】在数字世界的幕后,操作系统悄无声息地扮演着关键角色。本文将拨开迷雾,深入探讨操作系统中的两个基本概念——进程和线程。我们将通过生动的比喻和直观的解释,揭示它们之间的差异与联系,并展示如何在实际应用中灵活运用这些知识。准备好了吗?让我们开始这段揭秘之旅!
|
25天前
|
Java Unix 调度
python多线程!
本文介绍了线程的基本概念、多线程技术、线程的创建与管理、线程间的通信与同步机制,以及线程池和队列模块的使用。文章详细讲解了如何使用 `_thread` 和 `threading` 模块创建和管理线程,介绍了线程锁 `Lock` 的作用和使用方法,解决了多线程环境下的数据共享问题。此外,还介绍了 `Timer` 定时器和 `ThreadPoolExecutor` 线程池的使用,最后通过一个具体的案例展示了如何使用多线程爬取电影票房数据。文章还对比了进程和线程的优缺点,并讨论了计算密集型和IO密集型任务的适用场景。
47 4
|
25天前
|
调度 iOS开发 MacOS
python多进程一文够了!!!
本文介绍了高效编程中的多任务原理及其在Python中的实现。主要内容包括多任务的概念、单核和多核CPU的多任务实现、并发与并行的区别、多任务的实现方式(多进程、多线程、协程等)。详细讲解了进程的概念、使用方法、全局变量在多个子进程中的共享问题、启动大量子进程的方法、进程间通信(队列、字典、列表共享)、生产者消费者模型的实现,以及一个实际案例——抓取斗图网站的图片。通过这些内容,读者可以深入理解多任务编程的原理和实践技巧。
51 1
|
8天前
|
数据采集 Java Python
爬取小说资源的Python实践:从单线程到多线程的效率飞跃
本文介绍了一种使用Python从笔趣阁网站爬取小说内容的方法,并通过引入多线程技术大幅提高了下载效率。文章首先概述了环境准备,包括所需安装的库,然后详细描述了爬虫程序的设计与实现过程,包括发送HTTP请求、解析HTML文档、提取章节链接及多线程下载等步骤。最后,强调了性能优化的重要性,并提醒读者遵守相关法律法规。
40 0
|
24天前
|
NoSQL 关系型数据库 MySQL
python协程+异步总结!
本文介绍了Python中的协程、asyncio模块以及异步编程的相关知识。首先解释了协程的概念和实现方法,包括greenlet、yield关键字、asyncio装饰器和async/await关键字。接着详细讲解了协程的意义和应用场景,如提高IO密集型任务的性能。文章还介绍了事件循环、Task对象、Future对象等核心概念,并提供了多个实战案例,包括异步Redis、MySQL操作、FastAPI框架和异步爬虫。最后提到了uvloop作为asyncio的高性能替代方案。通过这些内容,读者可以全面了解和掌握Python中的异步编程技术。
42 0
下一篇
无影云桌面