python并发编程之协程

一、协程的概念

1、协程是单线程下的并发,它是程序员级别的,我们来控制如何切换。

2、进程的开销 >>>>>> 线程的开销 >>>>>> 协程的开销

3、协程的使用需要借助于第三方模块 gevent 模块或者 asyncio 模块

4、geventasyncio 是两个在 Python 中常用的协程框架,它们都提供了在异步编程中使用协程的能力。

  gevent 是一个基于协程的并发库,它通过 monkey-patching 的方式将标准库中的阻塞式 I/O 操作变为非阻塞的,从而实现协程的调度和并发处理。gevent 提供了自己的协程对象 Greenlet,使用 gevent.spawn() 来创建和管理协程。gevent 还提供了事件循环机制,允许在协程之间进行切换和调度。它适用于需要高性能网络编程的场景,如服务器开发。

  asyncio 是 Python 官方引入的异步编程框架,从 Python 3.4 版本开始引入,并在 Python 3.5 版本引入了 asyncawait 关键字。asyncio 基于事件循环(event loop)的概念,使用协程来实现异步编程。

它提供了异步 I/O 操作、定时器、任务调度等功能,并且有丰富的标准库和第三方库支持。asyncio 适用于编写高效的异步代码,特别是在 Web 开发、网络爬虫等场景下。

下面是一些比较 geventasyncio 的特点:

  • 生态系统和库支持: asyncio 有着更广泛的生态系统和库支持,包括官方标准库和第三方库,可以方便地与其他异步框架集成。gevent 虽然也有一些扩展库,但相对较少。

  • 编程模型和语法: gevent 使用了更传统的基于线程和阻塞 I/O 的编程模型,通过 monkey-patching 来实现协程。而 asyncio 使用了更现代的基于事件循环和异步 I/O 的编程模型,通过 asyncawait 关键字来定义协程。

  • 性能和吞吐量: 由于 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 操作替换为非阻塞的版本,从而实现协程的调度和并发处理。

具体解释如下:

  1. gevent 使用协程来实现并发处理,而协程的调度是由程序自身控制的。然而,标准库中的某些 I/O 操作(如 socket、select 等)是阻塞式的,它们会阻塞当前线程的执行,导致协程无法进行切换和调度。

  2. 为了解决这个问题,gevent 提供了 monkey.patch_all() 函数。调用 patch_all() 函数后,它会动态地修改标准库中的相关模块,将阻塞式的 I/O 操作替换为非阻塞的版本,从而实现协程的调度和并发处理。

  3. 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_threadthreading 模块中的一个函数,用于获取当前正在执行的线程对象。

具体含义如下:

  • 当你调用 current_thread() 函数时,它会返回当前线程的 Thread 对象。这个对象代表了当前执行的线程,可以通过它来获取线程的一些属性和调用线程的方法。

  • Thread 对象具有各种属性和方法,例如 name(线程的名称)、ident(线程的标识符)、is_alive()(判断线程是否存活)等。通过 current_thread() 返回的对象,你可以使用这些属性和方法来了解和操作当前线程的状态和行为。

  • current_thread() 函数可以在任何地方调用,包括主线程、子线程或其他线程的上下文中。它总是返回调用时的当前线程对象。

 

posted @ 2023-07-07 18:20  凡人半睁眼  阅读(80)  评论(0编辑  收藏  举报