python多線程和多進程以及協(xié)程詳解

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ì)看完一遍后會很有收獲

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末尊残,一起剝皮案震驚了整個濱河市炒瘸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寝衫,老刑警劉巖顷扩,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異慰毅,居然都是意外死亡隘截,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門事富,熙熙樓的掌柜王于貴愁眉苦臉地迎上來技俐,“玉大人乘陪,你說我怎么就攤上這事统台。” “怎么了啡邑?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵贱勃,是天一觀的道長。 經(jīng)常有香客問我谤逼,道長贵扰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任流部,我火速辦了婚禮戚绕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘枝冀。我一直安慰自己舞丛,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布果漾。 她就那樣靜靜地躺著球切,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绒障。 梳的紋絲不亂的頭發(fā)上吨凑,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天,我揣著相機與錄音户辱,去河邊找鬼鸵钝。 笑死糙臼,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的恩商。 我是一名探鬼主播弓摘,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼痕届!你這毒婦竟也來了韧献?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤研叫,失蹤者是張志新(化名)和其女友劉穎锤窑,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嚷炉,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡渊啰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了申屹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绘证。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖哗讥,靈堂內(nèi)的尸體忽然破棺而出嚷那,到底是詐尸還是另有隱情,我是刑警寧澤杆煞,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布魏宽,位于F島的核電站,受9級特大地震影響决乎,放射性物質(zhì)發(fā)生泄漏队询。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一构诚、第九天 我趴在偏房一處隱蔽的房頂上張望蚌斩。 院中可真熱鬧,春花似錦范嘱、人聲如沸送膳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肠缨。三九已至,卻和暖如春盏阶,著一層夾襖步出監(jiān)牢的瞬間晒奕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留脑慧,地道東北人魄眉。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像闷袒,于是被迫代替她去往敵國和親坑律。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 線程 操作系統(tǒng)線程理論 線程概念的引入背景 進程 之前我們已經(jīng)了解了操作系統(tǒng)中進程的概念,程序并不能單獨運行也物,只有...
    go以恒閱讀 1,645評論 0 6
  • 一. 操作系統(tǒng)概念 操作系統(tǒng)位于底層硬件與應(yīng)用軟件之間的一層.工作方式: 向下管理硬件,向上提供接口.操作系統(tǒng)進行...
    月亮是我踢彎得閱讀 5,971評論 3 28
  • 0三滑蚯、線程和進程的關(guān)系1浪蹂、一個進程可以有多個線程,但至少有一個線程告材;而一個線程只能在一個進程的地址空間內(nèi)活動坤次。2、...
    十二右閱讀 2,821評論 0 0
  • 多進程 要讓python程序?qū)崿F(xiàn)多進程斥赋,我們先了解操作系統(tǒng)的相關(guān)知識缰猴。 Unix、Linux操作系統(tǒng)提供了一個fo...
    蓓蓓的萬能男友閱讀 597評論 0 1
  • 寫在前面的話 代碼中的# > 表示的是輸出結(jié)果 輸入 使用input()函數(shù) 用法 注意input函數(shù)輸出的均是字...
    FlyingLittlePG閱讀 2,764評論 0 8