進程、線程和協程的調度和運行原理總結。html
linux的操做系統詳細調度策略可參考:http://blog.csdn.net/gatieme/article/details/51872659async
linux中的進程主要有三種調度策略:函數
優先級調度:將進程分爲普通進程和實時進程;
先進先出(隊列)調度:實時進程先建立的先執行,直到遇到io或主動阻塞。
輪轉調度(時間片):達到必定的CPU執行時間後強制切換;
多進程程序的調度其實仍是線程的調度,線程纔是CPU調度的基本單位;在同一個進程內線程切換不會產生進程切換,由一個進程內的線程切換到另外一個進程內的線程時,將會引發進程切換。
正在執行的進程執行完畢;
執行中進程發生阻塞;(如調用sleep)
執行中進程調用了P原語操做,從而因資源不足而被阻塞;或調用了v原語操做激活了等待資源的進程隊列;
執行中進程提出I/O請求後被阻塞;
CPU分配的時間片用完;(默認10ms)
就緒隊列中的某進程的優先級變得高於當前執行進程的優先級,引起強制切換;
若是咱們使用python建立了多進程或多線程,能夠認爲這幾個進程或線程是在公平隊列(即優先級相同)的實時進程,那麼其調度策略是FIFO和RR。
舉個例子,假設如今有一個單核的CPU,python程序建立了5個線程,這五個線程會按建立的時間前後進入到一個公平隊列中,CPU按先進先出原則開始執行第一個線程,若是遇到IO操做或休眠,或者執行這個線程的時間超過10ms;CUP就會中止當前線程,切換到第二個線程執行直到第五個線程;而後又從第一個線程開始循環,直到全部的線程執行完畢資源被操做系統回收。
固然,切換進程或線程也須要付出代價的,進程切換的代價大於線程。
進程:
建立一個進程後,每一個進程擁有本身獨立的內存地址空間,代碼段,數據段,BSS段,堆,棧等全部用戶空間的信息;
多進程中,子進程複製主進程的幾乎全部信息,除了pid等特殊信息;
線程:
一個進程下多個線程,多個線程共享進程的進程代碼段,進程的公有數據(堆),進程的所擁有其餘輔助資源;
各個線程獨立擁有的資源包括:線程id,程序計數器,一個棧,計數器寄存器和棧用來保存線程的執行歷史和執行狀態。
協程:
協程能夠看作輕量級的線程,即協程是在線程下開啓,多協程在單線程下實現併發,而操做系統最多隻能感知到線程,也就是說協程的切換對於操做系統來講是無感知的,屬於程序級別的切換;
多個協程共享單線程的代碼段、公有數據(堆)等;
每一個協程擁有本身的棧來保存上下文狀態,協程的切換開銷更小,對操做系統來講,會認爲一個開啓了多協程的線程一直在計算;
協程的優點在於切換的代價更小,所以CPU的有效利用率獲得了提升。
python的協程主流經過gevent和asyncio模塊實現,它們的核心原理都是底層用代碼建立事件循環來對多個協程的上下文進行調度;
python的代碼儘可能避免使用多線程;
若是上下文有一段代碼能夠分紅相對獨立的兩個部分,若是獨立的兩個部分是CPU密集型,那麼使用多進程;若是是IO密集型,那麼使用協程;若是二者都涉及,能夠考慮使用子進程中運行協程。
業務代碼爲了快速建立協程或進程,同時加強代碼的可讀性,推薦使用匿名函數。
from redis import StrictRedis rs = StrictRedis(host='192.168.1.20', port=6390, db=1) def get_rs1(): t = time.time() res = gevent.joinall([gevent.spawn(lambda x: gevent.sleep(2), x=i) for i in range(2)]) ls = [x.get() if x.kwargs['x'] == 1 else x.get() for x in res] print(ls) print(time.time() - t) if __name__ == '__main__': get_rs1() # gevent須要判斷返回的結果的順序