1. 程式人生 > >併發之協程

併發之協程

協程 Coroutine

據說, python官方沒有協程這個玩意兒 ,這是程式設計師自己鼓搗出來的, 更加輕量級(任務切換不是由自己的程式碼實現,而不是作業系統)
也叫微執行緒,  用這個可以讓自己的程式對CPU 的佔用率更高!避免被作業系統剝奪CPU的使用權.

對比作業系統控制執行緒的切換,使用者在單執行緒內控制協程的切換

優點如下:

#1. 協程的切換開銷更小,屬於程式級別的切換,作業系統完全感知不到,因而更加輕量級
#2. 單執行緒內就可以實現併發的效果,最大限度地利用cpu

缺點如下:

#1. 協程的本質是單執行緒下,無法利用多核,可以是一個程式開啟多個程序,每個程序內開啟多個執行緒,每個執行緒內開啟協程
#2. 協程指的是單個執行緒,因而一旦協程出現阻塞,將會阻塞整個執行緒

那麼做到協程起碼得實現以下兩點:

1. 可以控制多個任務之間的切換,切換之前將任務的狀態儲存下來,以便重新執行時,可以基於暫停的位置繼續執行。

2. 作為1的補充:可以檢測IO操作,在遇到io操作的情況下才發生切換

那如何去實現呢?

對於 1 . 可以使用yield  但是yield 不能識別阻塞

import time

def func1():
    while True:
        yield

def func2():
    g=func1()
    for i in range(10000000):
        i+1
        next(g)

start
=time.time() func2() stop=time.time() print(stop-start)
yield

如果我們在單個執行緒內有20個任務,要想實現在多個任務之間切換,使用yield生成器的方式過於麻煩(需要先得到初始化一次的生成器,然後再呼叫send。。。非常麻煩),而使用greenlet模組可以非常簡單地實現這20個任務直接的切換(這是個第三方模組,需要自己安裝)

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('jerry') # 可以在第一次switch時傳入引數,以後都不需要
greenlet

 但是以上兩者都不能實現識別到阻塞,並在阻塞前切換到

別慌,肯定有辦法的

import gevent, time
from gevent import monkey  # 幫助監測程式自己的阻塞


def micro_thread1():
    print("thread1 run...")
    print(time.time())
    # gevent.sleep(2)# 可以檢測到自己的阻塞
    monkey.patch_all()
    print("睡眠")
    time.sleep(1)  # 你以為你用的sleep 實際上間接用的 gevent裡的sleep  點patch_all()進去看
    print("thread1 run...")


def micro_thread2():
    print(time.time())
    time.sleep(3)  # 模擬IO操作耗時   在IO的時候  micro_thread1 還在執行
    print("thread2 run...")


g1 = gevent.spawn(micro_thread1)
g2 = gevent.spawn(micro_thread2)

gevent.joinall([g1, g2])  # 以後就都這樣寫 便不會因為單獨物件join()而出錯 這裡的join兼顧start() 而單獨的start是沒用的
gevent 與 monkey
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))
在爬蟲中的例項

 

 我們可以用threading.current_thread().getName()來檢視每個g1和g2,檢視的結果為DummyThread-n,即假執行緒