1. 程式人生 > >python併發,協程

python併發,協程

在作業系統中程序是資源分配的最小單位, 執行緒是CPU排程的最小單位。

協程:是單執行緒下的併發,又稱微執行緒,纖程。英文名Coroutine。一句話說明:協程是一種使用者態的輕量級執行緒,即協程是由使用者程式自己控制排程的。也就是說程式設計師用程式碼來控制切換.

參考: http://www.cnblogs.com/Eva-J/articles/8324673.html

複製程式碼
# 程序 啟動多個程序 程序之間是由作業系統負責呼叫
# 執行緒 啟動多個執行緒 真正被CPU執行的最小單位實際是執行緒
    # 開啟一個執行緒 建立一個執行緒 暫存器 堆疊
    # 關閉一個執行緒

# 協程
    # 本質上是一個執行緒
    # 能夠在多個任務之間切換來節省一些IO時間
    # 協程中任務之間的切換也消耗時間,但是開銷要遠遠小於程序執行緒之間的切換
# 實現併發的手段

import time
def consumer():
    while True:
        x = yield
        time.sleep(1)
        print('處理資料 :',x)

def producer():
    c = consumer()
    next(c)
    for i in range(10):
        time.sleep(1)
        print('生產資料:',i)
        c.send(i)

# 這個生產者消費者模型 模擬了程式的來回切換, 但是不能規避IO時間
producer()
複製程式碼

 

使用pip3 install greenlet 和 pip3 install gevent 安裝好模組,繼續:

複製程式碼
# 真正的協程模組就是使用greenlet完成的切換
from greenlet import greenlet

def eat():
    print('eating start')
    g2.switch()   # 切換到g2
    print('eating end')
    g2.switch()

def play():
    print('playing start')
    g1.switch()  # 切換到g1
    print('playing end')

g1 = greenlet(eat)  # 委託給g1
g2 = greenlet(play)
g1.switch()
複製程式碼
  • greenlet可以實現協程,不過每一次都要人為的去指向下一個該執行的協程,顯得太過麻煩。python還有一個比greenlet更強大的並且能夠自動切換任務的模組gevent

參考:https://www.cnblogs.com/PrettyTom/p/6628569.html

複製程式碼
# 協程是一種使用者態的輕量級執行緒,即協程是由使用者程式自己控制排程的。
import time
import gevent

def eat():
    print('eating start')
    # time.sleep(1)      # gevent 不能感知到time.sleep時間
    gevent.sleep(1)
    print('eating end')

def play():
    print('playing start')
    gevent.sleep(1)
    print('playing end')

g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
g1.join()
g2.join()
複製程式碼

 

gevent的正確方式:

複製程式碼
## 匯入這句,將所有模組中的阻塞IO都打成一個包。就可以感知 time.sleep
from gevent import monkey;monkey.patch_all()
import time
import gevent
import threading

def eat():
    print(threading.current_thread().getName())  # Dummy 假的,虛擬的。
    print(threading.current_thread())
    print('eating start')
    time.sleep(1.2)
    print('eating end')

def play():
    print(threading.current_thread().getName())
    print(threading.current_thread())
    print('playing start')
    time.sleep(1)
    print('playing end')

g1 = gevent.spawn(eat)   # 註冊到協程,遇到IO將自動切換
g2 = gevent.spawn(play)
# g1.join()
# g2.join()
gevent.joinall([g1,g2])
print('master')
# 程序和執行緒的任務切換由作業系統完成
# 協程任務之間的切換由程式(程式碼)完成,只有遇到協程模組能識別的IO操作的時候,程式才會進行任務切換,實現併發的效果
複製程式碼

 

同步和非同步:

複製程式碼
# 同步 和 非同步
from gevent import monkey;monkey.patch_all()
import time
import gevent

def task(n):
    time.sleep(1)
    print(n)

def sync():
    for i in range(5):
        task(i)

def async():
    g_lst = []
    for i in range(5):
        g = gevent.spawn(task,i)
        g_lst.append(g)
    gevent.joinall(g_lst)  # for g in g_lst:g.join()

sync()   # 同步
async()  # 非同步
複製程式碼

 

爬蟲時使用協程併發

複製程式碼
# 協程 : 能夠在一個執行緒中實現併發效果的概念
    #    能夠規避一些任務中的IO操作
    #    在任務的執行過程中,檢測到IO就切換到其他任務

# 多執行緒 被弱化了
# 協程 在一個執行緒上 提高CPU 的利用率
# 協程相比於多執行緒的優勢 切換的效率更快

# 爬蟲的例子
# 請求過程中的IO等待
from gevent import monkey;monkey.patch_all()
import gevent
from urllib.request import urlopen    # 內建的模組

def get_url(url):
    response = urlopen(url)
    content = response.read().decode('utf-8')
    return len(content)

g1 = gevent.spawn(get_url,'http://www.baidu.com')
g2 = gevent.spawn(get_url,'http://www.sogou.com')
g3 = gevent.spawn(get_url,'http://www.taobao.com')
g4 = gevent.spawn(get_url,'http://www.hao123.com')
g5 = gevent.spawn(get_url,'http://www.cnblogs.com')
gevent.joinall([g1,g2,g3,g4,g5])
print(g1.value)
print(g2.value)
print(g3.value)
print(g4.value)
print(g5.value)

ret = get_url('http://www.baidu.com')
print(ret)
複製程式碼