協程

協程

單線程如何實現併發:python

  • 多道技術:切換 + 保存狀態
# cpu正在運行一個任務,會在兩種狀況下切走去執行其餘的任務(切換由操做系統強制控制),
- 一種狀況是該任務發生了阻塞
- 另外一種狀況是該任務計算的時間過長或有一個優先級更高的程序替代了它

# 第二種狀況切換並不能提升效率,反而會下降效率。

# 基於以前的基礎知識,可使用yield,函數生成器來實現 單線程內程序間的切換和保存狀態
  • 單線程實現併發,須要在應用程序裏控制多個任務的切換+保存狀態
  • 優勢:應用程序級別速度要遠遠高於操做系統的切換
  • 缺點:多個任務一旦有一個IO阻塞沒有切,整個線程都阻塞在原地,該線程內的其餘的任務都不能執行了

單線程下,咱們不可避免程序中出現IO操做,但若是咱們能在本身的程序中(即用戶程序級別,而非操做系統級別)控制單線程下的多個任務,在一個任務遇到IO阻塞時就切換到另一個任務去計算,這樣就保證了該線程可以最大限度地處於就緒態,即隨時均可以被CPU執行的狀態,至關於咱們在用戶程序級別將本身的IO操做最大限度地隱藏起來,從而能夠迷惑操做系統,讓其看到:該線程好像是一直在計算,IO比較少,從而更多的將CPU的執行權限分配給咱們的線程。併發


協程是單線程下的併發,協程是一種用戶態的輕量級線程,即協程是由用戶程序本身控制調度的。socket

  • python的線程屬於內核級別的,即由操做系統控制調度,如單線程遇到io或執行時間過長就會被迫交出cpu執行權限,切換其餘線程運行函數

  • 單線程內開啓協程,一旦遇到io,就會從應用程序級別(而非操做系統)控制切換,以此來提高效率。spa

  • 協程的切換開銷更小,屬於程序級別的切換,操做系統徹底感知不到,於是更加輕量級;單線程內就能夠實現併發的效果,最大限度地利用cpu操作系統

  • 協程指的是單個線程,於是一旦協程出現阻塞,將會阻塞整個線程;線程

  • 協程的本質是單線程下,沒法利用多核,能夠是一個程序開啓多個進程,每一個進程內開啓多個線程,每一個線程內開啓協程code


greenlet模塊

greenlet模塊實現程序間的切換,缺點:遇到IO就阻塞server

greenlet只是提供了一種比generator更加便捷的切換方式,當切到一個任務執行時若是遇到io,那就原地阻塞,仍然是沒有解決遇到IO自動切換來提高效率的問題。協程

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時傳入參數,之後都不須要

gevent模塊

實現遇到gevent的IO自動切換

import gevent

def eat(name):
    print('%s eat 1' %name)
    gevent.sleep(2)				# 注意使用的是gevent.sleep(),而不是 time.sleep()
    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()		# #者gevent.joinall([g1, g2])
g2.join()
print('主')

猴子補丁實現全部IO的自動切換

  • from gevent import monkey;monkey.patch_all()放到文件的開頭
  • 這個必定要放在time.sleep 和socket這些io以前打補丁。
from gevent import monkey;monkey.patch_all()
import gevent
import time

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

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

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

協程:socket服務端併發

from gevent import spawn, monkey;monkey.patch_all()
import socket


def communication(conn):
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0: break
            conn.send(data.upper())
        except ConnectionResetError as e:
            print(e)
            break
    conn.close()


def server(ip, port):
    server = socket.socket()
    server.bind((ip, port))
    server.listen(5)
    while True:
        conn, addr = server.accept()
        spawn(communication, conn)		# 檢測IO, communication


if __name__ == '__main__':
    g1 = spawn(server, '127.0.0.1', 8080)	# 檢測IO,server
    g1.join()	# 等待g1運行結束,即一直在循環檢測io
相關文章
相關標籤/搜索