Python - 协程

1、简介

协程是单线程下的并发,又称微线程,纤程。英文名Coroutine。一句话说明什么是协程:协程是一种用户态的轻量级线程,即协程是由用户程序本身控制调度的。python

须要强调的是:git

# 1.python的线程属于内核级别的,即由操做系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cup执行权限,切换其它线程运行)
# 2.单线程内开户协程,一旦遇到io,就会从应用程序级别(而非操做系统)控制切换,以此来提高效率(非io操做的切换与效率无关)

对比操做系统控制线程的切换,用户在单线程内控制协程的切换github

优势以下:编程

# 1.协程的切换开销更小,属于程序级别的切换,操做系统彻底感知不到,于是更加轻量级
# 2.单线程内就能够实现并发的效果,最大限度地利用cpu

缺点以下:数组

# 1.协程的本质是单线程,没法利用多核,能够是一个程序开启多个进程,每一个进程内开启多个线程,每一个线程内开启协程
# 2.协程指的是单线程,于是一旦协程出现阻塞,将会阻塞整个线程

总结协程特色:并发

         1.必须在只有一个单线程里实现并发异步

         2.修改共享数据不须要加锁async

         3.用户程序里本身保存多个控制流的上下文栈异步编程

附加:一个协程遇到IO操做自动切换到其它协程(如何实现检测IO,yield,greenlet都没法实现,就用到了gevent模块(select机制))函数

2、Greenlet

若是咱们在单个线程内有20个任务,要想实如今多个任务之间切换,使用yield生成器的方式过于麻烦(须要先获得初始化一次的生成器,若是再调用send...很是麻烦),而使用greenlet模块能够很是简单地实现这20个任务直接的切换

安装:pip install greenlet

#! /usr/bin/env python3
# -*- coding:utf-8 -*-

# Author   : mayi
# Blog     : http://www.cnblogs.com/mayi0312/
# Date     : 2019/6/3
# Software : PyCharm

from greenlet import greenlet

def eat(name):
    print('%s eat 1' % name)
    g2.switch('egon')
    print('%s eat 2' % name)
    g2.switch()

def play(name):
    print('%s play 1' % name)
    g1.switch()
    print('%s play 2' % name)


g1 = greenlet(eat)
g2 = greenlet(play)

g1.switch('egon')  # 能够在第一次switch时传入参数,之后都不须要

单纯的切换(在没有io的状况下或者没有重复开辟内存空间的操做),反而会下降程序的执行速度

#! /usr/bin/env python3
# -*- coding:utf-8 -*-

# Author   : mayi
# Blog     : http://www.cnblogs.com/mayi0312/
# Date     : 2019/6/3
# Software : PyCharm

# 顺序执行
import time


def f1():
    res = 1
    for i in range(100000000):
        res += i


def f2():
    res = 1
    for i in range(100000000):
        res *= i


start = time.time()
f1()
f2()
stop = time.time()
print('run time is %s' % (stop - start))  # 15.185868501663208

print("------------------------------------------------")

# 切换
from greenlet import greenlet
import time


def f1():
    res = 1
    for i in range(100000000):
        res += i
        g2.switch()


def f2():
    res = 1
    for i in range(100000000):
        res *= i
        g1.switch()


start = time.time()
g1 = greenlet(f1)
g2 = greenlet(f2)
g1.switch()
stop = time.time()
print('run time is %s' % (stop - start))  # 64.25667524337769

greenlet只是提供了一种比generator更加便捷的切换方式,当一个任务执行时若是遇到io,那就原地阻塞,仍然是没有解决遇到io自动切换来提高效率的问题。

单线程里的这20个任务的代码一般会既有计算操做又有阻塞操做,咱们彻底能够在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2...如此,才能提升效率,这就用到了Gevent模块。

3、Gevent模块

安装:pip install gevent

Gevent是一个第三方库,能够轻松经过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet,这是以C扩展模块形式接入Python的轻量级协程。Greenlet所有运行在主程序操做系统进程的内部,但它们被协做式的调度。

#! /usr/bin/env python3
# -*- coding:utf-8 -*-

# Author   : mayi
# Blog     : http://www.cnblogs.com/mayi0312/
# Date     : 2019/6/3
# Software : PyCharm

import gevent


def eat(name):
    print('%s eat 1' % name)
    gevent.sleep(2)
    print('%s eat 2' % name)


def play(name):
    print('%s play 1' % name)
    gevent.sleep(1)
    print('%s play 2' % name)


g1 = gevent.spawn(eat, 'egon')
g2 = gevent.spawn(play, name='egon')
g1.join()
g2.join()
# 或者gevent.joinall([g1, g2])
print('')

遇到io阻塞时会自动切换任务

上例gevent.sleep(2)模拟的是gevent能够识别的阻塞,而time.sleep(2)或其它的阻塞,gevent是不能直接识别的,须要用下面一行代码,打补丁才能够识别

from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socke模块导入以前,或者干脆记忆成:要用gevent,须要将from gevent import monkey;monkey.patch_all()放到文件的开头

#! /usr/bin/env python3
# -*- coding:utf-8 -*-

# Author   : mayi
# Blog     : http://www.cnblogs.com/mayi0312/
# Date     : 2019/6/3
# Software : PyCharm

from gevent import monkey;monkey.patch_all()
import gevent
import time


def eat():
    print('eat food 1')
    time.sleep(2)
    print('eat food 2')


def play():
    print('play 1')
    time.sleep(1)
    print('play 2')


g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
gevent.joinall([g1, g2])
print('')

4、Gevent之同步与异步

#! /usr/bin/env python3
# -*- coding:utf-8 -*-

# Author   : mayi
# Blog     : http://www.cnblogs.com/mayi0312/
# Date     : 2019/6/3
# Software : PyCharm

from gevent import spawn, joinall, monkey;monkey.patch_all()
import time


def task(pid):
    """
    Some non-deterministic task
    """
    time.sleep(0.5)
    print('Task %s done' % pid)


def synchronous():
    for i in range(10):
        task(i)


def asynchronous():
    g_l = [spawn(task, i) for i in range(10)]
    joinall(g_l)


if __name__ == '__main__':
    print('Synchronous:')
    synchronous()

    print('Asynchronous:')
    asynchronous()
    # 上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。初始
    # 化的greenlet列表存放在数组threads中,此数组被传给gevent.joinall()函数,后
    # 者阻塞当前流程,并执行全部给定的greenlet。执行流程只会在全部greenlet执行
    # 完后才会继续向下走。

5、Gevent之应用举例

#! /usr/bin/env python3
# -*- coding:utf-8 -*-

# Author   : mayi
# Blog     : http://www.cnblogs.com/mayi0312/
# Date     : 2019/6/3
# Software : PyCharm
# Note     : 协程应用:爬虫

from gevent import monkey;monkey.patch_all()
import gevent
import requests
import time


def get_page(url):
    print('GET: %s' % url)
    response = requests.get(url)
    if response.status_code == 200:
        print('%d bytes received from %s' % (len(response.text), url))


start_time = time.time()
gevent.joinall([
    gevent.spawn(get_page, 'https://www.python.org/'),
    gevent.spawn(get_page, 'https://www.yahoo.com/'),
    gevent.spawn(get_page, 'https://github.com/'),
])
stop_time = time.time()
print('run time is %s' % (stop_time - start_time))
相关文章
相关标签/搜索