1. 程式人生 > >greenlet和gevent模塊

greenlet和gevent模塊

標記 soc == 死鎖 無需 host 本質 back down

協程是用戶態的線程,並非真正意義上的線程,

協程只有一個線程,看起來並發的效果是因為它利用了寄存器的上下文切換,

多線程和多進程比較消耗cpu資源,當遇到修改數據的時候,還會遇到死鎖的問題。

協程是最大的發揮了cpu的單核能力,遇到io阻塞就切換,阻塞完成之後切換回來。

協程的好處:

  • 跨平臺
  • 跨體系架構
  • 無需線程上下文切換的開銷
  • 無需原子操作鎖定及同步的開銷
  • 方便切換控制流,簡化編程模型
  • 高並發+高擴展性+低成本:一個CPU支持上萬的協程都不是問題。所以很適合用於高並發處理。

缺點:

  • 無法利用多核資源:協程的本質是個單線程,它不能同時將單個CPU 的多個核用上,協程需要和進程配合才能運行在多CPU上.當然我們日常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。
  • 進行阻塞(Blocking)操作(如IO時)會阻塞掉整個程序:這一點和事件驅動一樣,可以使用異步IO操作來解決

yield生成器實現協程的並發效果:

import time
"""
協程:是用戶態的線程
yield單線程下實現並發效果
"""

#生產者
def producer(name):
    con1.__next__()
    con2.__next__()
    count = 0
    while count < 5:
        time.sleep(1)
        print("\033[31;1m[producer %s]\033[0m is making baozi..." % name) 
        con1.send(count)
        con2.send(count)
        count += 1

#消費者
def consumer(name):        
    print("準備開始吃包子...")
    while True:
        baozi = yield
        print("[%s]吃了包子[%s]" % (name, baozi))


if __name__ == ‘__main__‘:
    con1 = consumer("軒軒")
    con2 = consumer("壯壯")
    producer("小白")

  

greenlet代碼示例:

from greenlet import greenlet


def homepage():
    print("34")
    gr2.switch()
    print("12")
    gr3.switch()

def bbs():
    print("84")
    gr3.switch()
    print("13")

def login():
    print("56")
    gr1.switch()
    print("--end--")




gr1 = greenlet(homepage)
gr2 = greenlet(bbs)
gr3 = greenlet(login)

  執行結果:

34
84
56
12
--end--

gevent模塊是greentlet模塊的簡單化:

import gevent
"""
自動檔的協程
自上而下從0開始計數,如果計數不大於sleep數,則跳過後面代碼
否則,執行後面代碼
"""

def run1():
    print("run1->1")
    gevent.sleep(2)
    print("run1->2")

def run2():
    print("run2->1")
    gevent.sleep(3)
    print("run2->2")

def run3():
    print("run3->1")
    gevent.sleep(1)
    print("run3->2")

def run4():
    print("run4->1")
    gevent.sleep(0)
    print("run4->2")

#由上而下依次執行
gevent.joinall([
    gevent.spawn(run1),
    gevent.spawn(run2),
    gevent.spawn(run3),
    gevent.spawn(run4),
])

  執行結果:

run1->1
run2->1
run3->1
run4->1
run4->2
run3->2
run1->2
run2->2

利用協程實現並發爬取網頁內容:

import gevent, time

from gevent import monkey
monkey.patch_all()

from urllib import request
"""
利用協程,高效爬取網頁
gevent模塊默認不識別urllib的IO操作,
需要導入monkey給urllib裏面的操作打上標記
"""
filename = "result.html"
#爬取網頁內容到本地
def get_url_content(url):
    print("GET: %s" % url)
    res = request.urlopen(url)
    html = res.read()
    with open(filename, "wb") as f:
        f.write(html)
    print("%d bytes received from %s" % (len(html), url))
    print("----------------------")

url_list = [
    "https://www.python.org/",
    "https://github.com/",
    "https://www.bilibili.com/"
]

#啟動時間
serial_start_time = time.time()
#串行的方式獲取網頁內容
for url in url_list:
    get_url_content(url)
#計算花費時間
print("serial cost:", time.time() - serial_start_time)


#啟動時間
async_start_time = time.time()
#並行的方式獲取網頁內容
gevent.joinall([
    gevent.spawn(get_url_content, "https://www.python.org/"),
    gevent.spawn(get_url_content, "https://github.com"),
    gevent.spawn(get_url_content, "https://www.bilibili.com/"),
])
#計算花費時間
print("async cost:", time.time() - async_start_time)

  協程實現高並發socket服務器:

服務端:

import gevent
from gevent import monkey
monkey.patch_all()
import socket
"""
協程實現socket的高並發服務器
"""


def handle_data(conn):
    try:
        while True:
            data = conn.recv(1024)
            if not data:
                conn.shutdown(socket.SHUT_WR)
            conn.send(data.upper())
    except EXception as ex:
        print(ex)
    finally:
        conn.close()

def my_server(port):
    server = socket.socket()
    #綁定地址和端口 
    server.bind(("0.0.0.0", port))
    #開始監聽
    server.listen(500)
    while True:
        #阻塞等待連接
        conn, addr = server.accept()
        #來數據了
        gevent.spawn(handle_data, conn)


if __name__ == ‘__main__‘:
    my_server(6666)

  客戶端:

import socket
"""
協程實現socket高並發的客戶端
"""


client = socket.socket()

client.connect(("localhost", 6666))


while True:
    input_data = input(">>:").strip()
    if not input_data:
        continue
    client.send(input_data.encode())
    data = client.recv(1024).decode()
    print(data)
client.close()

  

greenlet和gevent模塊