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))
相關文章
相關標籤/搜索