python并发编程之协程
一、协程的概念
1、协程是单线程下的并发,它是程序员级别的,我们来控制如何切换。
2、进程的开销 >>>>>> 线程的开销 >>>>>> 协程的开销
3、协程的使用需要借助于第三方模块 gevent 模块或者 asyncio 模块
4、gevent
和 asyncio
是两个在 Python 中常用的协程框架,它们都提供了在异步编程中使用协程的能力。
gevent
是一个基于协程的并发库,它通过 monkey-patching 的方式将标准库中的阻塞式 I/O 操作变为非阻塞的,从而实现协程的调度和并发处理。gevent
提供了自己的协程对象 Greenlet
,使用 gevent.spawn()
来创建和管理协程。gevent
还提供了事件循环机制,允许在协程之间进行切换和调度。它适用于需要高性能网络编程的场景,如服务器开发。
asyncio
是 Python 官方引入的异步编程框架,从 Python 3.4 版本开始引入,并在 Python 3.5 版本引入了 async
和 await
关键字。asyncio
基于事件循环(event loop)的概念,使用协程来实现异步编程。
它提供了异步 I/O 操作、定时器、任务调度等功能,并且有丰富的标准库和第三方库支持。asyncio
适用于编写高效的异步代码,特别是在 Web 开发、网络爬虫等场景下。
下面是一些比较 gevent
和 asyncio
的特点:
-
生态系统和库支持:
asyncio
有着更广泛的生态系统和库支持,包括官方标准库和第三方库,可以方便地与其他异步框架集成。gevent
虽然也有一些扩展库,但相对较少。 -
编程模型和语法:
gevent
使用了更传统的基于线程和阻塞 I/O 的编程模型,通过 monkey-patching 来实现协程。而asyncio
使用了更现代的基于事件循环和异步 I/O 的编程模型,通过async
和await
关键字来定义协程。 -
性能和吞吐量: 由于
gevent
使用了更轻量级的协程对象和调度机制,并且对阻塞 I/O 进行了优化,因此在某些特定的场景下,它可能会比asyncio
更高效和具有更高的吞吐量。
综上所述,选择使用 gevent
还是 asyncio
取决于具体的需求和场景。如果你需要高性能的网络编程和并发处理,可以选择使用 gevent
。而如果你需要一个官方支持、成熟且广泛应用的异步编程框架,并且需要与其他库和框架无缝集成,那么选择 asyncio
是一个不错的选择。
二、gevent的使用
1、服务端
from gevent import monkey monkey.patch_all() import gevent import socket def talk(conn): while True: try: data = conn.recv(1024) if len(data) == 0: break print(data) conn.send(data.upper()) except Exception as e: print(e) conn.close() def server(ip, port): server = socket.socket() # 实例化一个服务端 server.bind((ip, port)) server.listen(5) while True: conn, addr = server.accept() gevent.spawn(talk, conn) # gevent.spawn() 函数用于创建并启动一个协程,并返回一个 Greenlet 对象 # t=Process(target=talk,args=(conn,)) # t=Thread(target=talk,args=(conn,)) # t.start() if __name__ == '__main__': g1 = gevent.spawn(server, '127.0.0.1', 8080) # gevent.spawn() 函数用于创建并启动一个协程,并返回一个 Greenlet 对象 g1.join() ''' b'Thread-432 say hello' b'Thread-431 say hello' b'Thread-427 say hello' b'Thread-489 say hello' '''
猴子补丁的补充:
monkey.patch_all()
是 gevent
库中的一个函数,它的作用是将标准库中的阻塞式 I/O 操作替换为非阻塞的版本,从而实现协程的调度和并发处理。
具体解释如下:
-
gevent
使用协程来实现并发处理,而协程的调度是由程序自身控制的。然而,标准库中的某些 I/O 操作(如 socket、select 等)是阻塞式的,它们会阻塞当前线程的执行,导致协程无法进行切换和调度。 -
为了解决这个问题,
gevent
提供了monkey.patch_all()
函数。调用patch_all()
函数后,它会动态地修改标准库中的相关模块,将阻塞式的 I/O 操作替换为非阻塞的版本,从而实现协程的调度和并发处理。 -
monkey.patch_all()
函数会自动替换标准库中的一些模块,包括 socket、select、threading 等。它会将这些模块的阻塞式 I/O 操作替换为非阻塞的版本,使得协程可以在 I/O 操作阻塞时切换到其他协程执行,从而提高并发性能。
需要注意以下几点:
-
monkey.patch_all()
函数应该在程序的入口处调用,通常是在导入其他模块之前。这样可以确保所有的阻塞式 I/O 操作都被正确地替换。 -
monkey.patch_all()
函数会修改全局的 Python 环境,因此需要谨慎使用,特别是在与其他库和框架的集成中。如果某个库依赖于标准库的阻塞式 I/O 操作,可能会受到patch_all()
的影响。
总结起来,monkey.patch_all()
函数是 gevent
库中的一个工具函数,用于将标准库中的阻塞式 I/O 操作替换为非阻塞的版本,从而实现协程的调度和并发处理。它是使用 gevent
进行协程编程的常用方法之一,能够提高并发性能和效率。
2、客户端
import socket from threading import current_thread, Thread def socket_client(): client = socket.socket() client.connect(('127.0.0.1', 8080)) while True: ss = '%s say hello' % current_thread().getName() client.send(ss.encode('utf-8')) data = client.recv(1024) print(data) # 创建500个线程 for i in range(500): t = Thread(target=socket_client) # 实例化一个线程客户端 t.start() ''' b'THREAD-171 SAY HELLO' b'THREAD-416 SAY HELLO' b'THREAD-384 SAY HELLO' b'THREAD-37 SAY HELLO' '''
current_thread
是 threading
模块中的一个函数,用于获取当前正在执行的线程对象。
具体含义如下:
-
当你调用
current_thread()
函数时,它会返回当前线程的Thread
对象。这个对象代表了当前执行的线程,可以通过它来获取线程的一些属性和调用线程的方法。 -
Thread
对象具有各种属性和方法,例如name
(线程的名称)、ident
(线程的标识符)、is_alive()
(判断线程是否存活)等。通过current_thread()
返回的对象,你可以使用这些属性和方法来了解和操作当前线程的状态和行为。 -
current_thread()
函数可以在任何地方调用,包括主线程、子线程或其他线程的上下文中。它总是返回调用时的当前线程对象。