進(jìn)程
進(jìn)程定義(程序段,相關(guān)數(shù)據(jù)段和PCB構(gòu)成進(jìn)程實(shí)體,又稱為進(jìn)程映像):
- 進(jìn)程是程序的一次執(zhí)行
- 進(jìn)程是一個程序機(jī)器數(shù)據(jù)在處理機(jī)上順序執(zhí)行時所發(fā)生的活動
- 進(jìn)城是程序在一個數(shù)據(jù)集合上運(yùn)行的過程,它是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個獨(dú)立單位。
進(jìn)程的三種主要狀態(tài):
- 就緒態(tài)->當(dāng)進(jìn)程分配到除CPU以外所有必要資源后窍帝,只要再獲得CPU,便可立即執(zhí)行诽偷,進(jìn)程這時候的狀態(tài)稱為就緒狀態(tài)坤学。在一個系統(tǒng)中處于就緒狀態(tài)的進(jìn)程可能有多個,通常將它們排成一個隊列报慕,稱為就緒隊列深浮。
- 執(zhí)行狀態(tài)->進(jìn)程已獲得CPU,其程序正在執(zhí)行眠冈。在單處理機(jī)系統(tǒng)中飞苇,只有一個進(jìn)程處于執(zhí)行狀態(tài)菌瘫,在多處理機(jī)系統(tǒng)中,則有多個進(jìn)程處于執(zhí)行狀態(tài)布卡。
- 阻塞狀態(tài)->正在執(zhí)行的進(jìn)程由于發(fā)生某件事請而暫時無法繼續(xù)執(zhí)行時雨让,便放棄處理機(jī)而處于暫停狀態(tài),亦即進(jìn)程的執(zhí)行受到阻塞忿等,把這種暫停狀態(tài)稱為阻塞狀態(tài)栖忠,有時也稱為等待狀態(tài)狀態(tài)或封鎖狀態(tài)。典型事件:請求I/O贸街,申請緩沖空間等庵寞。
進(jìn)程是一個實(shí)體。每一個進(jìn)程都有它自己的地址空間薛匪,一般情況下捐川,包括文本區(qū)域(text region)、數(shù)據(jù)區(qū)域(data region)和堆棧(stack region)逸尖。文本區(qū)域存儲處理器執(zhí)行的代碼属拾;數(shù)據(jù)區(qū)域存儲變量和進(jìn)程執(zhí)行期間使用的動態(tài)分配的內(nèi)存;堆棧區(qū)域存儲著活動過程調(diào)用的指令和本地變量冷溶。
特征
動態(tài)性:進(jìn)程的實(shí)質(zhì)是程序在多道程序系統(tǒng)中的一次執(zhí)行過程渐白,進(jìn)程是動態(tài)產(chǎn)生,動態(tài)消亡的逞频。
并發(fā)性:任何進(jìn)程都可以同其他進(jìn)程一起并發(fā)執(zhí)行
獨(dú)立性:進(jìn)程是一個能獨(dú)立運(yùn)行的基本單位纯衍,同時也是系統(tǒng)分配資源和調(diào)度的獨(dú)立單位;
異步性:由于進(jìn)程間的相互制約苗胀,使進(jìn)程具有執(zhí)行的間斷性襟诸,即進(jìn)程按各自獨(dú)立的、不可預(yù)知的速度向前推進(jìn)
結(jié)構(gòu)特征:進(jìn)程由程序基协、數(shù)據(jù)和進(jìn)程控制塊三部分組成歌亲。
多個不同的進(jìn)程可以包含相同的程序:一個程序在不同的數(shù)據(jù)集里就構(gòu)成不同的進(jìn)程,能得到不同的結(jié)果澜驮;但是執(zhí)行過程中陷揪,程序不能發(fā)生改變。
線程
線程杂穷,有時被稱為輕量級進(jìn)程(Lightweight Process悍缠,LWP),是程序執(zhí)行流的最小單元耐量。一個標(biāo)準(zhǔn)的線程由線程ID飞蚓,當(dāng)前指令指針(PC),寄存器集合和堆棧組成廊蜒。另外趴拧,線程是進(jìn)程中的一個實(shí)體溅漾,是被系統(tǒng)獨(dú)立調(diào)度和分派的基本單位,線程自己不擁有系統(tǒng)資源著榴,只擁有一點(diǎn)兒在運(yùn)行中必不可少的資源樟凄,但它可與同屬一個進(jìn)程的其它線程共享進(jìn)程所擁有的全部資源。
線程是程序中一個單一的順序控制流程兄渺。進(jìn)程內(nèi)有一個相對獨(dú)立的缝龄、可調(diào)度的執(zhí)行單元,是系統(tǒng)獨(dú)立調(diào)度和分派CPU的基本單位指令運(yùn)行時的程序的調(diào)度單位挂谍。在單個程序中同時運(yùn)行多個線程完成不同的工作叔壤,稱為多線程。
特點(diǎn)
在多線程OS中口叙,通常是在一個進(jìn)程中包括多個線程炼绘,每個線程都是作為利用CPU的基本單位,是花費(fèi)最小開銷的實(shí)體妄田。線程具有以下屬性俺亮。
1)輕型實(shí)體
線程中的實(shí)體基本上不擁有系統(tǒng)資源,只是有一點(diǎn)必不可少的疟呐、能保證獨(dú)立運(yùn)行的資源脚曾。
線程的實(shí)體包括程序、數(shù)據(jù)和TCB启具。線程是動態(tài)概念本讥,它的動態(tài)特性由線程控制塊TCB(Thread Control Block)描述。TCB包括以下信息:
(1)線程狀態(tài)鲁冯。
(2)當(dāng)線程不運(yùn)行時拷沸,被保存的現(xiàn)場資源。
(3)一組執(zhí)行堆棧薯演。
(4)存放每個線程的局部變量主存區(qū)撞芍。
(5)訪問同一個進(jìn)程中的主存和其它資源。
用于指示被執(zhí)行指令序列的程序計數(shù)器跨扮、保留局部變量序无、少數(shù)狀態(tài)參數(shù)和返回地址等的一組寄存器和堆棧。
2)獨(dú)立調(diào)度和分派的基本單位好港。
在多線程OS中愉镰,線程是能獨(dú)立運(yùn)行的基本單位米罚,因而也是獨(dú)立調(diào)度和分派的基本單位钧汹。由于線程很“輕”,故線程的切換非常迅速且開銷新荚瘛(在同一進(jìn)程中的)拔莱。
3)可并發(fā)執(zhí)行碗降。
在一個進(jìn)程中的多個線程之間,可以并發(fā)執(zhí)行塘秦,甚至允許在一個進(jìn)程中所有線程都能并發(fā)執(zhí)行讼渊;同樣,不同進(jìn)程中的線程也能并發(fā)執(zhí)行尊剔,充分利用和發(fā)揮了處理機(jī)與外圍設(shè)備并行工作的能力爪幻。
4)共享進(jìn)程資源。
在同一進(jìn)程中的各個線程须误,都可以共享該進(jìn)程所擁有的資源挨稿,這首先表現(xiàn)在:所有線程都具有相同的地址空間(進(jìn)程的地址空間),這意味著京痢,線程可以訪問該地址空間的每一個虛地址奶甘;此外,還可以訪問進(jìn)程所擁有的已打開文件祭椰、定時器臭家、信號量機(jī)構(gòu)等。由于同一個進(jìn)程內(nèi)的線程共享內(nèi)存和文件方淤,所以線程之間互相通信不必調(diào)用內(nèi)核钉赁。
進(jìn)程是資源單位,線程是執(zhí)行單位
協(xié)程
協(xié)程 携茂,又稱為微線程橄霉,它是實(shí)現(xiàn)多任務(wù)的另一種方式,只不過是比線程更小的執(zhí)行單元邑蒋。因?yàn)樗詭PU的上下文姓蜂,這樣只要在合適的時機(jī),我們可以把一個協(xié)程切換到另一個協(xié)程医吊。
通俗的理解: 在一個線程中的某個函數(shù)中钱慢,我們可以在任何地方保存當(dāng)前函數(shù)的一些臨時變量等信息,然后切換到另外一個函數(shù)中執(zhí)行卿堂,注意不是通過調(diào)用函數(shù)的方式做到的 束莫,并且切換的次數(shù)以及什么時候再切換到原來的函數(shù)都由開發(fā)者自己確定。
協(xié)程與線程的差異:
在實(shí)現(xiàn)多任務(wù)時, 線程切換從系統(tǒng)層面遠(yuǎn)不止保存和恢復(fù)CPU上下文這么簡單草描。操作系統(tǒng)為了程序運(yùn)行的高效性览绿,每個線程都有自己緩存Cache等等數(shù)據(jù),操作系統(tǒng)還會幫你做這些數(shù)據(jù)的恢復(fù)操作穗慕,所以線程的切換非常耗性能饿敲。但是協(xié)程的切換只是單純地操作CPU的上下文,所以一秒鐘切換個上百萬次系統(tǒng)都抗的住逛绵。
協(xié)程通過在線程中實(shí)現(xiàn)調(diào)度怀各,避免了陷入內(nèi)核級別的上下文切換造成的性能損失倔韭,進(jìn)而突破了線程在IO上的性能瓶頸。協(xié)程避免了無意義的調(diào)度瓢对,由此可以提高性能寿酌,但也因此,程序員必須自己承擔(dān)調(diào)度的責(zé)任硕蛹,同時醇疼,協(xié)程也失去了標(biāo)準(zhǔn)線程使用多CPU的能力
進(jìn)程,線程法焰,協(xié)程三者上下文切換比較
進(jìn)程 | 線程 | 協(xié)程 | |
---|---|---|---|
切換者 | 操作系統(tǒng) | 操作系統(tǒng) | 用戶(編程者/應(yīng)用程序) |
切換時機(jī) | 根據(jù)操作系統(tǒng)自己的切換策略僵腺,用戶不感知 | 根據(jù)操作系統(tǒng)自己的切換策略,用戶不感知 | 用戶自己的程序決定 |
切換內(nèi)容 | 頁全局目錄壶栋,內(nèi)核棧辰如,硬件上下文 | 內(nèi)核棧,硬件上下文 | 硬件上下文 |
切換內(nèi)容的保存 | 保存于內(nèi)核棧中 | 保存于內(nèi)核棧中 | 保存在用戶自己的變量(用戶椆笫裕或堆) |
切換過程 | 用戶態(tài) - 內(nèi)核態(tài) - 用戶態(tài) | 用戶態(tài) - 內(nèi)核態(tài) - 用戶態(tài) | 用戶態(tài)(沒用進(jìn)入內(nèi)核態(tài)) |
切換效率 | 低 | 中 | 高 |
在python中琉兜,yield(生成器)可以很容易的實(shí)現(xiàn)上述的功能,從一個函數(shù)切換到另外一個函數(shù)毙玻。
Python的協(xié)程源于yield指令豌蟋。yield有兩個功能:
- yield item用于產(chǎn)出一個值,反饋給next()的調(diào)用方桑滩。
- 作出讓步梧疲,暫停執(zhí)行生成器,讓調(diào)用方繼續(xù)工作运准,直到需要使用另一個值時再調(diào)用next()幌氮。
import time
def task_1():
while True:
print("--This is task 1!--before")
yield
print("--This is task 1!--after")
time.sleep(0.5)
def task_2():
while True:
print("--This is task 2!--before")
yield
print("--This is task 2!--after")
time.sleep(0.5)
if __name__ == "__main__":
t1 = task_1() # 生成器對象
t2 = task_2()
# print(t1, t2)
while True:
next(t1) # 1、喚醒生成器t1胁澳,執(zhí)行到y(tǒng)ield后该互,保存上下文,掛起任務(wù)韭畸;下次再次喚醒之后宇智,從yield繼續(xù)往下執(zhí)行
print("\nThe main thread!\n") # 2、繼續(xù)往下執(zhí)行
next(t2) # 3胰丁、喚醒生成器t2随橘,....
實(shí)現(xiàn)協(xié)作式多任務(wù),在Python3.5正式引入了 async/await表達(dá)式,使得協(xié)程正式在語言層面得到支持和優(yōu)化锦庸,大大簡化之前的yield寫法机蔗。
import asyncio
async def compute(x, y):
print("Compute %s + %s ..." % (x, y))
await asyncio.sleep(x + y)
return x + y
async def print_sum(x, y):
result = await compute(x, y)
print("%s + %s = %s" % (x, y, result))
loop = asyncio.get_event_loop()
tasks = [print_sum(1, 2), print_sum(3, 4)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
'''
1、event_loop 事件循環(huán):相當(dāng)于一個無限循環(huán),我們可以把一些函數(shù)注冊到這個事件循環(huán)上蜒车,當(dāng)滿足條件時讳嘱,就會調(diào)用對應(yīng)的處理方法幔嗦。
2酿愧、coroutine 協(xié)程:協(xié)程對象,只一個使用async關(guān)鍵字定義的函數(shù)邀泉,他的調(diào)用不會立即執(zhí)行函數(shù)嬉挡,而是會返回一個協(xié)程對象。協(xié)程對象需要注冊到事件循環(huán)中汇恤,由事件循環(huán)調(diào)用庞钢。
3、task 任務(wù):一個協(xié)程對象就是一個原生可以掛起的函數(shù)因谎,任務(wù)則是對協(xié)程的進(jìn)一步封裝基括,其中包含任務(wù)的各種狀態(tài)。
4财岔、future:代表將來執(zhí)行或沒有執(zhí)行的任務(wù)結(jié)果风皿。它與task沒有本質(zhì)的區(qū)別。
5匠璧、async/await 關(guān)鍵字:python3.5用于定義協(xié)程的關(guān)鍵字桐款,async定義一個協(xié)程,await用于掛起阻塞的異步調(diào)用接口夷恍。
6魔眨、run_until_complete:根據(jù)傳遞的參數(shù)的不同,返回的結(jié)果也有所不同
①酿雪、run_until_complete()傳遞的是一個協(xié)程對象或task對象遏暴,則返回他們finished的返回結(jié)果(前提是他們得 有return的結(jié)果,否則返回None)
②指黎、run_until_complete(asyncio.wait(多個協(xié)程對象或任務(wù)))拓挥,函數(shù)會返回一個元組包括(done, pending),通過訪問done里的task對象袋励,獲取返回值
③侥啤、run_until_complete(asyncio.gather(多個協(xié)程對象或任務(wù))),函數(shù)會返回一個列表茬故,列表里面包括各個任 務(wù)的的返回結(jié)果盖灸,按順序排列
'''
協(xié)程是對線程的調(diào)度,yield類似惰性求值方式可以視為一種流程控制工具磺芭,
線程是內(nèi)核進(jìn)行搶占式的調(diào)度的赁炎,這樣就確保了每個線程都有執(zhí)行的機(jī)會。
而 coroutine 運(yùn)行在同一個線程中,由語言的運(yùn)行時中的 EventLoop(事件循環(huán))來進(jìn)行調(diào)度徙垫。
和大多數(shù)語言一樣讥裤,在 Python 中,協(xié)程的調(diào)度是非搶占式的姻报,也就是說一個協(xié)程必須主動讓出執(zhí)行機(jī)會己英,其他協(xié)程才有機(jī)會運(yùn)行。
讓出執(zhí)行的關(guān)鍵字就是 await吴旋。也就是說一個協(xié)程如果阻塞了损肛,持續(xù)不讓出 CPU,那么整個線程就卡住了荣瑟,沒有任何并發(fā)治拿。
Gevent
-----
Python通過yield
提供了對協(xié)程的基本支持,但是不完全笆焰。而第三方的gevent為Python提供了比較完善的協(xié)程支持劫谅。
Gevent是一個第三方庫,可以輕松通過gevent實(shí)現(xiàn)并發(fā)同步或異步編程嚷掠,在gevent中主要用到的模式是Greenlet捏检,它是以C擴(kuò)展模塊形式接入Python的輕量級協(xié)程。Greenlet全部運(yùn)行在主程序操作系統(tǒng)的內(nèi)部叠国,被協(xié)作式調(diào)度未檩。
gevent其基本思想是:
當(dāng)一個greenlet遇到IO操作時,比如訪問網(wǎng)絡(luò)粟焊,就自動切換到其他的greenlet冤狡,等到IO操作完成,再在適當(dāng)?shù)臅r候切換回來繼續(xù)執(zhí)行项棠。由于IO操作非常耗時悲雳,經(jīng)常使程序處于等待狀態(tài),有了gevent為我們自動切換協(xié)程香追,就保證總有g(shù)reenlet在運(yùn)行合瓢,而不是等待IO。
由于切換是在IO操作時自動完成透典,所以gevent需要修改Python自帶的一些標(biāo)準(zhǔn)庫晴楔,這一過程在啟動時通過monkey patch完成:
from gevent import monkey; monkey.patch_socket()
import gevent
def f(n):
for i in range(n):
print gevent.getcurrent(), i
g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()
from gevent import monkey; monkey.patch_all()
import gevent
import urllib2
def f(url):
print('GET: %s' % url)
resp = urllib2.urlopen(url)
data = resp.read()
print('%d bytes received from %s.' % (len(data), url))
gevent.joinall([
gevent.spawn(f, 'https://www.python.org/'),
gevent.spawn(f, 'https://www.yahoo.com/'),
gevent.spawn(f, 'https://github.com/'),
])
協(xié)程的同步寫法卻能擁有異步的性能
這個問題實(shí)際上在上面已經(jīng)得到解答了。雖然協(xié)程的使用是通過同步的寫法實(shí)現(xiàn)的峭咒。如果希望得到性能上的提升税弃,實(shí)際上還是通過異步回調(diào)的支持,只不過這個異步回調(diào)是底層模型上完成的凑队。所以上協(xié)程上關(guān)于同步異步的性能是建立在底層的異步模型上的则果。
協(xié)程占用的空間可以比線程小
協(xié)程是用戶定義的,所以在設(shè)計協(xié)程時,協(xié)程的最小空間占用可以比線程小很多西壮。在一個服務(wù)器很難創(chuàng)建10W個線程遗增,但是可以輕松的創(chuàng)建10W個協(xié)程。
協(xié)程切換比線程的切換更輕量
先控制一下變量款青,不考慮阻塞的問題
假設(shè)4核的cpu做修,100個任務(wù)并發(fā)執(zhí)行
(1)創(chuàng)建100個線程:100個線程參與cpu調(diào)度。
(2)創(chuàng)建10個線程可都,每個線程創(chuàng)建10個協(xié)程:10個線程參與cpu調(diào)度缓待,每個協(xié)程內(nèi)的10個協(xié)程由協(xié)程庫自己進(jìn)行調(diào)度蚓耽。
理論上渠牲,進(jìn)程擁有100個線程時,每個線程的時間片會比擁有10個線程時短步悠,也就意味著在相同時間里签杈,擁有100個線程時的上下文切換次數(shù)比擁有10個線程時多。
所以協(xié)程并發(fā)模型與多線程同步模型相比鼎兽,在一定條件下會減少線程切換次數(shù)答姥,但是增加了協(xié)程切換次數(shù),由于協(xié)程的切換是由協(xié)程庫調(diào)度的谚咬,所以很難說協(xié)程切換的代價比省去的線程切換代價小鹦付,合理的方式應(yīng)該是通過測試工具在具體的業(yè)務(wù)場景得出一個最好的平衡點(diǎn)。
將task函數(shù)封裝到Greenlet內(nèi)部線程的gevent.spawn择卦。 初始化的greenlet列表存放在數(shù)組threads中敲长,此數(shù)組被傳給gevent.joinall 函數(shù),后者阻塞當(dāng)前流程秉继,并執(zhí)行所有給定的greenlet祈噪。執(zhí)行流程只會在 所有g(shù)reenlet執(zhí)行完后才會繼續(xù)向下走。