• Python协程之Gevent模块


    背景

    进程是操作系统分配资源的最小单位,每个进程独享4G的内存地址空间,因此进程内数据是安全的,检查间的通信需要使用特定的方法。同理,正是因为进程是数据安全的,所以导致进程的切换是一个很麻烦效率不高的操作。为了解决进程切换带来的问题,线程这个名词出现了,一个进程可以包含多个线程,一个进程下的所有线程共享所有的数据,数据可以直接访问,协程的切换比进程的切换更快。进程和线程的切换是有操作系统控制,不是应用程序自己控制,是被动的。为了开发出应用程序自己可控制,百万级任务切换的方案,协程这个概念被提出了。

    协程是一个“轻”量级的任务,协程的切换是单纯的应用程序内的代码变动,不涉及操作系统的动作。一个线程可以包含多个协程,协程的并发可以极大的提升代码的运行效率,而且协程的并发不用考虑互斥锁的问题,共享全局数据更简单更安全。

    线程适用的场景:

    • IO密集型操作的系统,协程遇见IO操作自动切换

    • 百万级任务的切换

    python和协程

    python因为GIL锁的原因,使得python环境下的多线程达不到理想的效果,因为GIL锁保证了同一时间下只有一个线程运行。为了实现python环境下多任务的并发,一般是多进程+多协程。python从3.0版本开始开发自带的协程库asyncio,到3.7版本已经发展成熟到了一定程度,完全可以用于线上运行。在asyncio库出来以前,第三方库Gevent是一个非常优秀的协程库,一直到现在还在被广泛使用。

    Gevent库

    例子:Gevent实现socket服务端和客户端之间的通信,以及使用queue进行协程见的消息通信。

    from gevent import monkey; monkey.patch_all()
    import gevent
    import queue
    import time
    from socket import *
    
    q = queue.Queue()
    
    def func1():
        while True:
            msg = q.get()
            print("func1 : %s" % (msg, ))
    
    def func2():
        s = socket(AF_INET, SOCK_STREAM)
        s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        s.bind(("127.0.0.1", 5555))
        s.listen(5)
        while True:
            conn, addr = s.accept()
            while True:
                res = conn.recv(1024)
                q.put(res)
                conn.send("ok".encode('utf-8'))
    
    gevent.joinall([
        gevent.spawn(func1),
        gevent.spawn(func2)
    ])
    from socket import *
    
    client = socket(AF_INET, SOCK_STREAM)
    client.connect(('127.0.0.1', 5555))
    
    while True:
        msg = input('>>: ').strip()
        client.send(msg.encode('utf-8'))
        msg = client.recv(1024)
        print(msg)

    注意:

    • Gevent实现遇见IO自动切换的功能,需要调用如下代码from gevent import monkey; monkey.patch_all()实现,且处于该代码下面的代码才能自动切换,处于代码前面的代码不能自动切换
    • 使用queue时,如果协程的等待IO全部是在从queue获取数据,那么Gevent会报异常“gevent.exceptions.LoopExit: This operation would block forever”,如下例子。解决的办法是不让Gevent全部等待在queue的get操作即可,如上面的socket例子。
    from gevent import monkey; monkey.patch_all()
    import gevent
    import queue
    import time
    from socket import *
    
    q = queue.Queue()
    
    def func1():
        while True:
            msg = q.get()
            print("func1 : %s" % (msg, ))
    
    def func2():
        q.put("abc".encode('utf-8'))
        time.sleep(1)
        q.put("def".encode('utf-8'))
    
    gevent.joinall([
        gevent.spawn(func1),
        gevent.spawn(func2)
    ])

    asyncio库(python3.7以上)

    import asyncio
    
    async def foo():
        print('----start foo')
        await asyncio.sleep(1)
        print('----end foo')
    
    async def bar():
        print('****start bar')
        await asyncio.sleep(2)
        print('****end bar')
    
    async def main():
        # res = await asyncio.gather(foo(), bar()) # 所有的协程并发执行
        # 等价
        task1 = asyncio.create_task(foo())
        task2 = asyncio.create_task(bar())
        await task1
        await task2
    
    if __name__ == '__main__':
        asyncio.run(main())
      
    # 结果  
    ----start foo
    ****start bar
    ----end foo
    ****end bar

    将上述socker例子,使用asyncio开发,如下所示。

    import asyncio

    async def myprint(): while True: msg = await q.get() print("myprint: %s" % msg) q.task_done() async def handle(reader, writer): while True: data = await reader.read(100) message = data.decode() addr = writer.get_extra_info('peername') await q.put(f"Received {message!r} from {addr!r}") writer.write("ok".encode('UTF-8')) await writer.drain() async def myserver(): server = await asyncio.start_server(handle, '127.0.0.1', 8888) async with server: await server.serve_forever() async def main():
    global q
    q = asyncio.Queue()
    await asyncio.gather(myserver(), myprint()) asyncio.run(main())
    import asyncio
    
    async def tcp_echo_client(message):
        reader, writer = await asyncio.open_connection('127.0.0.1', 8888)
    
        while True:
            msg = input(">>")
            writer.write(msg.encode('UTF-8'))
    
            data = await reader.read(100)
            print(f'Received: {data.decode()!r}')
    
    
    asyncio.run(tcp_echo_client('Hello World!'))
  • 相关阅读:
    Logstash IIS日志采集
    Logstash_Apache日志采集
    k8s 资源管理
    获取hdfs集群信息(fs.defaultFS)
    PHP CMS的pc标签
    流程
    PHP复习
    权限管理
    注册审核
    简单的文件管理程序练习
  • 原文地址:https://www.cnblogs.com/chusiyong/p/12855384.html
Copyright © 2020-2023  润新知