進程、線程和協程的調度和運行原理總結。
系列文章
進程、線程的調度策略介紹
linux的操作系統詳細調度策略可參考:http://blog.csdn.net/gatieme/article/details/51872659
linux中的進程主要有三種調度策略:
-
優先級調度:將進程分為普通進程和實時進程;
-
先進先出(隊列)調度:實時進程先創建的先執行,直到遇到io或主動阻塞。
-
輪轉調度(時間片):達到一定的CPU執行時間后強制切換;
多進程程序的調度其實還是線程的調度,線程才是CPU調度的基本單位;在同一個進程內線程切換不會產生進程切換,由一個進程內的線程切換到另一個進程內的線程時,將會引起進程切換。
引起進程or線程調度的原因
-
正在執行的進程執行完畢;
-
執行中進程發生阻塞;(如調用sleep)
-
執行中進程調用了P原語操作,從而因資源不足而被阻塞;或調用了v原語操作激活了等待資源的進程隊列;
-
執行中進程提出I/O請求后被阻塞;
-
CPU分配的時間片用完;(默認10ms)
-
就緒隊列中的某進程的優先級變得高於當前執行進程的優先級,引發強制切換;
linux下python進程or線程調度
如果我們使用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需要判斷返回的結果的順序