1 概念梳理:
1.1 線程
1.1.1 什么是線程
線程是操作系統(tǒng)能夠進行運算調(diào)度的最小單位。它被包含在進程之中栅炒,是進程中的實際運作單位广鳍。一條線程指的是進程中一個單一順序的控制流耗绿,一個進程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務(wù)储藐。一個線程是一個execution context(執(zhí)行上下文)俱济,即一個cpu執(zhí)行時所需要的一串指令。
1.1.2 線程的工作方式
假設(shè)你正在讀一本書钙勃,沒有讀完蛛碌,你想休息一下,但是你想在回來時恢復(fù)到當(dāng)時讀的具體進度辖源。有一個方法就是記下頁數(shù)蔚携、行數(shù)與字?jǐn)?shù)這三個數(shù)值,這些數(shù)值就是execution context克饶。如果你的室友在你休息的時候酝蜒,使用相同的方法讀這本書。你和她只需要這三個數(shù)字記下來就可以在交替的時間共同閱讀這本書了矾湃。
線程的工作方式與此類似亡脑。CPU會給你一個在同一時間能夠做多個運算的幻覺,實際上它在每個運算上只花了極少的時間邀跃,本質(zhì)上CPU同一時刻只干了一件事霉咨。它能這樣做就是因為它有每個運算的execution context。就像你能夠和你朋友共享同一本書一樣坞嘀,多任務(wù)也能共享同一塊CPU躯护。
1.2 進程
一個程序的執(zhí)行實例就是一個進程惊来。每一個進程提供執(zhí)行程序所需的所有資源丽涩。(進程本質(zhì)上是資源的集合)
一個進程有一個虛擬的地址空間、可執(zhí)行的代碼、操作系統(tǒng)的接口矢渊、安全的上下文(記錄啟動該進程的用戶和權(quán)限等等)继准、唯一的進程ID、環(huán)境變量矮男、優(yōu)先級類移必、最小和最大的工作空間(內(nèi)存空間),還要有至少一個線程毡鉴。
每一個進程啟動時都會最先產(chǎn)生一個線程崔泵,即主線程。然后主線程會再創(chuàng)建其他的子線程猪瞬。
與進程相關(guān)的資源包括:
內(nèi)存頁(同一個進程中的所有線程共享同一個內(nèi)存空間)
文件描述符(e.g. open sockets)
安全憑證(e.g.啟動該進程的用戶ID)
1.3 進程與線程區(qū)別
1.同一個進程中的線程共享同一內(nèi)存空間憎瘸,但是進程之間是獨立的。
2.同一個進程中的所有線程的數(shù)據(jù)是共享的(進程通訊)陈瘦,進程之間的數(shù)據(jù)是獨立的幌甘。
3.對主線程的修改可能會影響其他線程的行為,但是父進程的修改(除了刪除以外)不會影響其他子進程痊项。
4.線程是一個上下文的執(zhí)行指令锅风,而進程則是與運算相關(guān)的一簇資源。
5.同一個進程的線程之間可以直接通信鞍泉,但是進程之間的交流需要借助中間代理來實現(xiàn)皱埠。
6.創(chuàng)建新的線程很容易,但是創(chuàng)建新的進程需要對父進程做一次復(fù)制咖驮。
7.一個線程可以操作同一進程的其他線程漱逸,但是進程只能操作其子進程。
8.線程啟動速度快游沿,進程啟動速度慢(但是兩者運行速度沒有可比性)饰抒。
2 多線程
2.1 線程常用方法
方法注釋
start()線程準(zhǔn)備就緒,等待CPU調(diào)度
setName()為線程設(shè)置名稱
getName()獲取線程名稱
setDaemon(True)設(shè)置為守護線程
join()逐個執(zhí)行每個線程诀黍,執(zhí)行完畢后繼續(xù)往下執(zhí)行
run()線程被cpu調(diào)度后自動執(zhí)行線程對象的run方法袋坑,如果想自定義線程類,直接重寫run方法就行了
2.1.1 Thread類
1.普通創(chuàng)建方式
import threadingimport timedefrun(n): print("task", n)
? ? time.sleep(1)
? ? print('2s')
? ? time.sleep(1)
? ? print('1s')
? ? time.sleep(1)
? ? print('0s')
? ? time.sleep(1)
t1 = threading.Thread(target=run, args=("t1",))
t2 = threading.Thread(target=run, args=("t2",))
t1.start()
t2.start()"""
task t1
task t2
2s
2s
1s
1s
0s
0s
"""
2.繼承threading.Thread來自定義線程類
其本質(zhì)是重構(gòu)Thread類中的run方法
i
import threading
import timeclass
MyThread(threading.Thread):?
? ? def__init__(self, n):?
? ? ? ? ?super(MyThread, self).__init__() # 重構(gòu)run函數(shù)必須要寫?
? ? ? ? ?self.n = n
? ? defrun(self):? ? ? ??
? ? ? ? print("task", self.n)
? ? ? ? time.sleep(1)
? ? ? ? print('2s')
? ? ? ? time.sleep(1)
? ? ? ? print('1s')
? ? ? ? time.sleep(1)
? ? ? ? print('0s')
? ? ? ? time.sleep(1)if __name__ == "__main__":
? ? t1 = MyThread("t1")
? ? t2 = MyThread("t2")
? ? t1.start()
? ? t2.start()
2.1.2 計算子線程執(zhí)行的時間
注:sleep的時候是不會占用cpu的,在sleep的時候操作系統(tǒng)會把線程暫時掛起眯勾。
join()#等此線程執(zhí)行完后枣宫,再執(zhí)行其他線程或主線程threading.current_thread()#輸出當(dāng)前線程
import threading
import time
defrun(n):?
? ? print("task", n,threading.current_thread()) #輸出當(dāng)前的線程 time.sleep(1)
? ? print('3s')
? ? time.sleep(1)
? ? print('2s')
? ? time.sleep(1)
? ? print('1s')
strat_time = time.time()
t_obj = []? #定義列表用于存放子線程實例for i in range(3):
? ? t = threading.Thread(target=run, args=("t-%s" % i,))
? ? t.start()
? ? t_obj.append(t)
? ? """
由主線程生成的三個子線程
task t-0 <Thread(Thread-1, started 44828)>
task t-1 <Thread(Thread-2, started 42804)>
task t-2 <Thread(Thread-3, started 41384)>
"""for tmp in t_obj:
? ? t.join()? ? ? ? ? ? #為每個子線程添加join之后,主線程就會等這些子線程執(zhí)行完之后再執(zhí)行吃环。print("cost:", time.time() - strat_time) #主線程print(threading.current_thread())? ? ? #輸出當(dāng)前線程"""
<_MainThread(MainThread, started 43740)>
"""
由主線程生成的三個子線程
task t-0 <Thread(Thread-1, started 44828)>
task t-1 <Thread(Thread-2, started 42804)>
task t-2 <Thread(Thread-3, started 41384)>
"""fortmpint_obj:? ? t.join()#為每個子線程添加join之后也颤,主線程就會等這些子線程執(zhí)行完之后再執(zhí)行。print("cost:", time.time() - strat_time)#主線程print(threading.current_thread())#輸出當(dāng)前線程"""
<_MainThread(MainThread, started 43740)>
"""
2.1.3 統(tǒng)計當(dāng)前活躍的線程數(shù)
由于主線程比子線程快很多郁轻,當(dāng)主線程執(zhí)行active_count()時翅娶,其他子線程都還沒執(zhí)行完畢文留,因此利用主線程統(tǒng)計的活躍的線程數(shù)num = sub_num(子線程數(shù)量)+1(主線程本身)
import threading
import time
defrun(n):?
? ? print("task", n)
? ? time.sleep(1)? ? ? #此時子線程停1sfor i in range(3):
? ? t = threading.Thread(target=run, args=("t-%s" % i,))
? ? t.start()
time.sleep(0.5)? ? #主線程停0.5秒print(threading.active_count()) #輸出當(dāng)前活躍的線程數(shù)"""
task t-0
task t-1
task t-2
4
"""
此外我們還能發(fā)現(xiàn)在python內(nèi)部默認(rèn)會等待最后一個進程執(zhí)行完后再執(zhí)行exit(),或者說python內(nèi)部在此時有一個隱藏的join()竭沫。
2.2 守護進程
我們看下面這個例子燥翅,這里使用setDaemon(True)把所有的子線程都變成了主線程的守護線程,因此當(dāng)主進程結(jié)束后蜕提,子線程也會隨之結(jié)束森书。所以當(dāng)主線程結(jié)束后,整個程序就退出了谎势。
import threading
import time
defrun(n):?
? ? print("task", n)
? ? time.sleep(1)? ? ? #此時子線程停1s? ? print('3')
? ? time.sleep(1)
? ? print('2')
? ? time.sleep(1)
? ? print('1')for i in range(3):
? ? t = threading.Thread(target=run, args=("t-%s" % i,))
? ? t.setDaemon(True)? #把子進程設(shè)置為守護線程凛膏,必須在start()之前設(shè)置? ? t.start()
time.sleep(0.5)? ? #主線程停0.5秒print(threading.active_count()) #輸出活躍的線程數(shù)"""
task t-0
task t-1
task t-2
4
Process finished with exit code 0
"""
2.3 GIL
在非python環(huán)境中,單核情況下脏榆,同時只能有一個任務(wù)執(zhí)行译柏。多核時可以支持多個線程同時執(zhí)行。但是在python中姐霍,無論有多少核鄙麦,同時只能執(zhí)行一個線程。究其原因镊折,這就是由于GIL的存在導(dǎo)致的胯府。
GIL的全稱是Global Interpreter Lock(全局解釋器鎖),來源是python設(shè)計之初的考慮恨胚,為了數(shù)據(jù)安全所做的決定骂因。某個線程想要執(zhí)行,必須先拿到GIL赃泡,我們可以把GIL看作是“通行證”寒波,并且在一個python進程中,GIL只有一個升熊。拿不到通行證的線程俄烁,就不允許進入CPU執(zhí)行。GIL只在cpython中才有级野,因為cpython調(diào)用的是c語言的原生線程页屠,所以他不能直接操作cpu,只能利用GIL保證同一時間只能有一個線程拿到數(shù)據(jù)蓖柔。而在pypy和jpython中是沒有GIL的辰企。
Python多線程的工作過程:
python在使用多線程的時候,調(diào)用的是c語言的原生線程况鸣。
拿到公共數(shù)據(jù)
申請gil
python解釋器調(diào)用os原生線程
os操作cpu執(zhí)行運算
當(dāng)該線程執(zhí)行時間到后牢贸,無論運算是否已經(jīng)執(zhí)行完,gil都被要求釋放
進而由其他進程重復(fù)上面的過程
等其他進程執(zhí)行完后镐捧,又會切換到之前的線程(從他記錄的上下文繼續(xù)執(zhí)行)
整個過程是每個線程執(zhí)行自己的運算潜索,當(dāng)執(zhí)行時間到就進行切換(context switch)臭增。
python針對不同類型的代碼執(zhí)行效率也是不同的:
1、CPU密集型代碼(各種循環(huán)處理帮辟、計算等等),在這種情況下玩焰,由于計算工作多由驹,ticks計數(shù)很快就會達到閾值互艾,然后觸發(fā)GIL的釋放與再競爭(多個線程來回切換當(dāng)然是需要消耗資源的)祭椰,所以python下的多線程對CPU密集型代碼并不友好。
2他嚷、IO密集型代碼(文件處理默刚、網(wǎng)絡(luò)爬蟲等涉及文件讀寫的操作)甥郑,多線程能夠有效提升效率(單線程下有IO操作會進行IO等待,造成不必要的時間浪費荤西,而開啟多線程能在線程A等待時澜搅,自動切換到線程B,可以不浪費CPU的資源邪锌,從而能提升程序執(zhí)行效率)勉躺。所以python的多線程對IO密集型代碼比較友好。
使用建議觅丰?
python下想要充分利用多核CPU饵溅,就用多進程。因為每個進程有各自獨立的GIL妇萄,互不干擾蜕企,這樣就可以真正意義上的并行執(zhí)行,在python中冠句,多進程的執(zhí)行效率優(yōu)于多線程(僅僅針對多核CPU而言)轻掩。
GIL在python中的版本差異:
1、在python2.x里懦底,GIL的釋放邏輯是當(dāng)前線程遇見IO操作或者ticks計數(shù)達到100時進行釋放放典。(ticks可以看作是python自身的一個計數(shù)器,專門做用于GIL基茵,每次釋放后歸零奋构,這個計數(shù)可以通過sys.setcheckinterval 來調(diào)整)。而每次釋放GIL鎖拱层,線程進行鎖競爭弥臼、切換線程,會消耗資源根灯。并且由于GIL鎖存在径缅,python里一個進程永遠只能同時執(zhí)行一個線程(拿到GIL的線程才能執(zhí)行)掺栅,這就是為什么在多核CPU上,python的多線程效率并不高纳猪。
2氧卧、在python3.x中,GIL不使用ticks計數(shù)氏堤,改為使用計時器(執(zhí)行時間達到閾值后沙绝,當(dāng)前線程釋放GIL),這樣對CPU密集型程序更加友好鼠锈,但依然沒有解決GIL導(dǎo)致的同一時間只能執(zhí)行一個線程的問題闪檬,所以效率依然不盡如人意。
2.4 線程鎖
由于線程之間是進行隨機調(diào)度购笆,并且每個線程可能只執(zhí)行n條執(zhí)行之后粗悯,當(dāng)多個線程同時修改同一條數(shù)據(jù)時可能會出現(xiàn)臟數(shù)據(jù),所以同欠,出現(xiàn)了線程鎖样傍,即同一時刻允許一個線程執(zhí)行操作。線程鎖用于鎖定資源铺遂,你可以定義多個鎖, 像下面的代碼, 當(dāng)你需要獨占某一資源時铭乾,任何一個鎖都可以鎖這個資源,就好比你用不同的鎖都可以把相同的一個門鎖住是一個道理娃循。
由于線程之間是進行隨機調(diào)度炕檩,如果有多個線程同時操作一個對象,如果沒有很好地保護該對象捌斧,會造成程序結(jié)果的不可預(yù)期笛质,我們也稱此為“線程不安全”。
#實測:在python2.7捞蚂、mac os下妇押,運行以下代碼可能會產(chǎn)生臟數(shù)據(jù)。但是在python3中就不一定會出現(xiàn)下面的問題姓迅。
import threading
import time
defrun(n):?
? ? global num
? ? num += 1num = 0t_obj = [] for i in range(20000):
? ? t = threading.Thread(target=run, args=("t-%s" % i,))
? ? t.start()
? ? t_obj.append(t)for t in t_obj:
? ? t.join()print "num:", num"""
產(chǎn)生臟數(shù)據(jù)后的運行結(jié)果:
num: 19999
"""
2.5 互斥鎖(mutex)
為了方式上面情況的發(fā)生敲霍,就出現(xiàn)了互斥鎖(Lock)
import threading
import time
defrun(n):?
? ? lock.acquire() #獲取鎖 global num
? ? num += 1? ? lock.release()? #釋放鎖lock = threading.Lock()? ? #實例化一個鎖對象num = 0t_obj = []? for i in range(20000):
? ? t = threading.Thread(target=run, args=("t-%s" % i,))
? ? t.start()
? ? t_obj.append(t)for t in t_obj:
? ? t.join()print "num:", num
2.6 遞歸鎖
RLcok類的用法和Lock類一模一樣,但它支持嵌套丁存,肩杈,在多個鎖沒有釋放的時候一般會使用使用RLcok類。
import threadingimport time
gl_num = 0?
lock = threading.RLock()
? defFunc():? ? lock.acquire()
? ? global gl_num
? ? gl_num +=1? ? time.sleep(1)
? ? print gl_num
? ? lock.release()
? ? ? for i in range(10):
? ? t = threading.Thread(target=Func)
? ? t.start()
2.7 信號量(BoundedSemaphore類)
互斥鎖同時只允許一個線程更改數(shù)據(jù)解寝,而Semaphore是同時允許一定數(shù)量的線程更改數(shù)據(jù) 扩然,比如廁所有3個坑,那最多只允許3個人上廁所聋伦,后面的人只能等里面有人出來了才能再進去夫偶。
import threading
import time
defrun(n):?
? ? semaphore.acquire() #加鎖 time.sleep(1)
? ? print("run the thread:%s\n" % n)
? ? semaphore.release()? ? #釋放num = 0semaphore = threading.BoundedSemaphore(5)? # 最多允許5個線程同時運行
for i in range(22):
? ? t = threading.Thread(target=run, args=("t-%s" % i,))
? ? t.start()while threading.active_count() != 1:
? ? pass? # print threading.active_count()else:
? ? print('-----all threads done-----')
2.8 事件(Event類)
python線程的事件用于主線程控制其他線程的執(zhí)行界睁,事件是一個簡單的線程同步對象,其主要提供以下幾個方法:
方法注釋
clear將flag設(shè)置為“False”
set將flag設(shè)置為“True”
is_set判斷是否設(shè)置了flag
wait會一直監(jiān)聽flag兵拢,如果沒有檢測到flag就一直處于阻塞狀態(tài)
事件處理的機制:全局定義了一個“Flag”翻斟,當(dāng)flag值為“False”,那么event.wait()就會阻塞说铃,當(dāng)flag值為“True”访惜,那么event.wait()便不再阻塞。
#利用Event類模擬紅綠燈
import threading
import time
event = threading.Event()
deflighter():? ??
? ? count = 0? ? event.set()? ? #初始值為綠燈? ? while True:
? ? ? ? if 5 < count <=10 :
? ? ? ? ? ? event.clear()? # 紅燈截汪,清除標(biāo)志位? ? ? ? ? ? print("\33[41;1mred light is on...\033[0m")
? ? ? ? elif count > 10:
? ? ? ? ? ? event.set()? # 綠燈疾牲,設(shè)置標(biāo)志位? ? ? ? ? ? count = 0? ? ? ? else:
? ? ? ? ? ? print("\33[42;1mgreen light is on...\033[0m")
? ? ? ? time.sleep(1)
? ? ? ? count += 1defcar(name):? ? while True:
? ? ? ? if event.is_set():? ? ? #判斷是否設(shè)置了標(biāo)志位? ? ? ? ? ? print("[%s] running..."%name)
? ? ? ? ? ? time.sleep(1)
? ? ? ? else:
? ? ? ? ? ? print("[%s] sees red light,waiting..."%name)
? ? ? ? ? ? event.wait()
? ? ? ? ? ? print("[%s] green light is on,start going..."%name)
light = threading.Thread(target=lighter,)
light.start()
car = threading.Thread(target=car,args=("MINI",))
car.start()
2.9 條件(Condition類)
使得線程等待植捎,只有滿足某條件時衙解,才釋放n個線程
2.10 定時器(Timer類)
定時器,指定n秒后執(zhí)行某操作
from threading import Timer
defhello():? ? print("hello, world")
t = Timer(1, hello)
t.start()? # after 1 seconds, "hello, world" will be printed
3 多進程
在linux中焰枢,每個進程都是由父進程提供的蚓峦。每啟動一個子進程就從父進程克隆一份數(shù)據(jù),但是進程之間的數(shù)據(jù)本身是不能共享的济锄。
from multiprocessing?
import Process
import time
deff(name): time.sleep(2)
? ? print('hello', name)
if __name__ == '__main__':
? ? p = Process(target=f, args=('bob',))
? ? p.start()
? ? p.join()
from multiprocessing?
import Process
import os
definfo(title):? ? print(title)
? ? print('module name:', __name__)
? ? print('parent process:', os.getppid())? #獲取父進程id? ??
? ? print('process id:', os.getpid())? #獲取自己的進程id? ? print("\n\n")
deff(name):? ? info('\033[31;1mfunction f\033[0m')
? ? print('hello', name)
if __name__ == '__main__':
? ? info('\033[32;1mmain process line\033[0m')
? ? p = Process(target=f, args=('bob',))
? ? p.start()
? ? p.join()
3.1 進程間通信
由于進程之間數(shù)據(jù)是不共享的暑椰,所以不會出現(xiàn)多線程GIL帶來的問題。多進程之間的通信通過Queue()或Pipe()來實現(xiàn)
3.1.1 Queue()
使用方法跟threading里的queue差不多
from multiprocessing?
import Process, Queue
deff(q):? ? q.put([42, None, 'hello'])
if __name__ == '__main__':
? ? q = Queue()
? ? p = Process(target=f, args=(q,))
? ? p.start()
? ? print(q.get())? ? # prints "[42, None, 'hello']"? ? p.join()
3.1.2 Pipe()
Pipe的本質(zhì)是進程之間的數(shù)據(jù)傳遞荐绝,而不是數(shù)據(jù)共享一汽,這和socket有點像。pipe()返回兩個連接對象分別表示管道的兩端低滩,每端都有send()和recv()方法召夹。如果兩個進程試圖在同一時間的同一端進行讀取和寫入那么,這可能會損壞管道中的數(shù)據(jù)恕沫。
from multiprocessing?
import Process, Pipe
deff(conn):? ??
? ? conn.send([42, None, 'hello'])
? ? conn.close()
if __name__ == '__main__':
? ? parent_conn, child_conn = Pipe()
? ? p = Process(target=f, args=(child_conn,))
? ? p.start()
? ? print(parent_conn.recv())? # prints "[42, None, 'hello']"? ? p.join()
3.2 Manager
通過Manager可實現(xiàn)進程間數(shù)據(jù)的共享监憎。Manager()返回的manager對象會通過一個服務(wù)進程,來使其他進程通過代理的方式操作python對象婶溯。manager對象支持?list,?dict,?Namespace,?Lock,?RLock,?Semaphore,?BoundedSemaphore,?Condition,?Event,?Barrier,?Queue,?Value?,Array.
from multiprocessing?
import Process, Manager
deff(d, l):? ??
? ? d[1] = '1'? ? d['2'] = 2? ? d[0.25] = None? ? l.append(1)
? ? print(l)
if __name__ == '__main__':
? ? with Manager() as manager:
? ? ? ? d = manager.dict()
? ? ? ? l = manager.list(range(5))
? ? ? ? p_list = []
? ? ? ? for i in range(10):
? ? ? ? ? ? p = Process(target=f, args=(d, l))
? ? ? ? ? ? p.start()
? ? ? ? ? ? p_list.append(p)
? ? ? ? for res in p_list:
? ? ? ? ? ? res.join()
? ? ? ? print(d)
? ? ? ? print(l)
3.3 進程鎖(進程同步)
數(shù)據(jù)輸出的時候保證不同進程的輸出內(nèi)容在同一塊屏幕正常顯示鲸阔,防止數(shù)據(jù)亂序的情況。
Without using the lock output from the different processes is liable to get all mixed up.
from multiprocessing?
import Process, Lock
deff(l, i):? ??
? ? l.acquire()
? ? try:
? ? ? ? print('hello world', i)
? ? finally:
? ? ? ? l.release()
if __name__ == '__main__':
? ? lock = Lock()
? ? for num in range(10):
? ? ? ? Process(target=f, args=(lock, num)).start()
3.4 進程池
由于進程啟動的開銷比較大迄委,使用多進程的時候會導(dǎo)致大量內(nèi)存空間被消耗褐筛。為了防止這種情況發(fā)生可以使用進程池,(由于啟動線程的開銷比較小叙身,所以不需要線程池這種概念死讹,多線程只會頻繁得切換cpu導(dǎo)致系統(tǒng)變慢,并不會占用過多的內(nèi)存空間)
進程池中常用方法:
apply()?同步執(zhí)行(串行)
apply_async()?異步執(zhí)行(并行)
terminate()?立刻關(guān)閉進程池
join()?主進程等待所有子進程執(zhí)行完畢曲梗。必須在close或terminate()之后赞警。
close()?等待所有進程結(jié)束后妓忍,才關(guān)閉進程池。
from multiprocessing?
import Process,Poolimport time
defFoo(i):? ??
? ? time.sleep(2)
? ? return i+100 defBar(arg):? ??
? ? print('-->exec done:',arg)
pool = Pool(5)? #允許進程池同時放入5個進程 for i in range(10):
? ? pool.apply_async(func=Foo, args=(i,),callback=Bar)? #func子進程執(zhí)行完后愧旦,才會執(zhí)行callback世剖,否則callback不執(zhí)行(而且callback是由父進程來執(zhí)行了)? ? #pool.apply(func=Foo, args=(i,))
print('end')
pool.close()
pool.join() #主進程等待所有子進程執(zhí)行完畢。必須在close()或terminate()之后笤虫。
進程池內(nèi)部維護一個進程序列旁瘫,當(dāng)使用時,去進程池中獲取一個進程琼蚯,如果進程池序列中沒有可供使用的進程酬凳,那么程序就會等待,直到進程池中有可用進程為止遭庶。在上面的程序中產(chǎn)生了10個進程宁仔,但是只能有5同時被放入進程池,剩下的都被暫時掛起峦睡,并不占用內(nèi)存空間翎苫,等前面的五個進程執(zhí)行完后,再執(zhí)行剩下5個進程榨了。
4 補充:協(xié)程
線程和進程的操作是由程序觸發(fā)系統(tǒng)接口煎谍,最后的執(zhí)行者是系統(tǒng),它本質(zhì)上是操作系統(tǒng)提供的功能龙屉。而協(xié)程的操作則是程序員指定的呐粘,在python中通過yield,人為的實現(xiàn)并發(fā)處理转捕。
協(xié)程存在的意義:對于多線程應(yīng)用作岖,CPU通過切片的方式來切換線程間的執(zhí)行,線程切換時需要耗時瓜富。協(xié)程鳍咱,則只使用一個線程,分解一個線程成為多個“微線程”与柑,在一個線程中規(guī)定某個代碼塊的執(zhí)行順序谤辜。
協(xié)程的適用場景:當(dāng)程序中存在大量不需要CPU的操作時(IO)。
常用第三方模塊gevent和greenlet价捧。(本質(zhì)上丑念,gevent是對greenlet的高級封裝,因此一般用它就行结蟋,這是一個相當(dāng)高效的模塊脯倚。)
4.1 greenlet
from greenlet import greenlet
deftest1():?
? ? print(12)
? ? gr2.switch()
? ? print(34)
? ? gr2.switch()deftest2():? ? print(56)
? ? gr1.switch()
? ? print(78)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
實際上,greenlet就是通過switch方法在不同的任務(wù)之間進行切換。
4.2 gevent
from gevent import monkey; monkey.patch_all()
import geventimport requests
deff(url):?
? ? print('GET: %s' % url)
? ? resp = requests.get(url)
? ? data = resp.text
? ? 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/'),
])
通過joinall將任務(wù)f和它的參數(shù)進行統(tǒng)一調(diào)度推正,實現(xiàn)單線程中的協(xié)程恍涂。代碼封裝層次很高,實際使用只需要了解它的幾個主要方法即可植榕。
原文章轉(zhuǎn)載出處:https://www.cnblogs.com/whatisfantasy/p/6440585.html
這篇文章寫的很贊再沧,轉(zhuǎn)載了一下,細(xì)細(xì)看完一遍后會很有收獲