python (协程/线程/进程) (四)协程

协程,又称微线程,是用户级的轻量级线程。协程拥有自己的寄存器上下文和栈,调度切换时,将寄存器上下文保存在其他地方,切回来恢复。因此,协程能保留上一次调用的状态。

  • 在并发编程中,协程与线程类似,每个协程有自己的本地数据,与其他协程共享全局数据和其他资源
  • 协程需要用户自己编写调度逻辑,对CPU来说,协程其实是单线程,CPU不需要考虑怎样调度。

python 通过yield提供了对协程的基本支持,但不完全,而使用第三方gevent库是更好的选择。gevent是基于协程的python网络函数库。

  • 使用greenlet在libev事件循环顶部提供了一个高级别并发性的api,主要特点:

    • 基于libev的快速事件循环,linux上是epoll机制
    • 基于greenlet的轻量级执行单元
    • API复用了python标准库里的内容
    • 支持SSL的协作式sockets
    • 可通过线程池或c-ares实现DNS查询
  • 通过monkey patching功能使得第三方模块变成协作式。

    • gevent对协程的支持,本质上是greenlet在实现切换工作。greenlet工作流程如下:

      • 假如进行访问网络IO操作时,出现阻塞,greenlet就显示切换到另一段没有被阻塞的代码段执行,直到原先的阻塞状况消失,再自动切回原来的代码段继续执行。串行方式

      • 有了gevent为我们自动切换协程,就保证greenlet在运行,而不是等待IO。这就是协程比一般多线程高效的原因

      • 由于切换是在IO操作时自动完成,所以gevent需要修改python自带的一些标准库,将一些常见的阻塞,如socket、select等地方实现跳转,这一过程启动通过monkey patch完成

一、简易

以下程序主要用了 gevent 中的 spawn 方法 joinall 方法spawn方法 可以看做是用来形成协程joinall 方法 就是添加这些协程任务,并且启动运行。三个网络操作时并发执行的,而且结束顺序不同,但其实只有一个线程

#!coding:utf-8
from gevent import monkey
monkey.patch_all() # 修改python自带的一些标准库,将一些常见的阻塞实现跳转

import gevent
import urllib3

def run_task(url):
    print("Visit --> %s" % url)
    try:
        http = urllib3.PoolManager() # 由该实例对象处理与线程池的连接以及线程安全的所有细节
        response = http.request('GET', url)
        data = response.data.decode()
        print('%d bytes received from %s.' % (len(data), url))

    except Exception as e:
        print(e)

if __name__ == '__main__':
    urls = ['https://github.com/', 'https://www.csdn.net/', 'http://www.baidu.com/']
    # 用来形成协程
    greenlets = [gevent.spawn(run_task,url) for url in urls] #主要用了gevent中的spawn 方法和joinall 方法,spawn方法可以看做是用来形成协程,joinall 方法就是添加这些协程任务,并且启动运行。
    # 添加这协程任务,并且启动运行
    gevent.joinall(greenlets)

二、进阶

gevent 中也提供了对池的支持,对 greenlet 进行 并发管理限制并发数),就可以使用池,这在处理大量的网络和IO操作时时非常必要的,程序改写如下:

#!coding:utf-8
from gevent import monkey
monkey.patch_all() # 修改python自带的一些标准库,将一些常见的阻塞实现跳转

import gevent
from gevent.pool import Pool
import urllib3

def run_task(url):
    print("Visit --> %s" % url)
    try:
        http = urllib3.PoolManager() # 由该实例对象处理与线程池的连接以及线程安全的所有细节
        response = http.request('GET', url)
        data = response.data.decode()
        print('%d bytes received from %s.' % (len(data), url))

    except Exception as e:
        print(e)
    return 'url:%s ---> finish' % url

if __name__ == '__main__':
    urls = ['https://github.com/', 'https://www.csdn.net/', 'http://www.baidu.com/']

    pool =Pool(2)
    results = pool.map(run_task, urls)
    print(results)

猜你喜欢

转载自blog.csdn.net/qq_19707521/article/details/107689924