python入門系列:多進(jìn)程

多進(jìn)程和多線程的區(qū)別

Python多線程的操作熔吗,由于有GIL鎖的存在镇饺,使得其運(yùn)行效率并不會很高匙睹,無法充分利用 多核cpu 的優(yōu)勢,只有在I/O密集形的任務(wù)邏輯中才能實(shí)現(xiàn)并發(fā)瑟由。

使用多進(jìn)程來編寫同樣消耗cpu(一般是計(jì)算)的邏輯媒鼓,對于 多核cpu 來說效率會好很多。

操作系統(tǒng)對進(jìn)程的調(diào)度代價(jià)要比線程調(diào)度要大的多。

多線程和多進(jìn)程使用案例對比

1.用多進(jìn)程多線程兩種方式來運(yùn)算 斐波那契數(shù)列绿鸣,這里都依賴 concurrent.futures 模塊提供的線/進(jìn)程池疚沐。

import time

from concurrent.futures import ThreadPoolExecutor

from concurrent.futures import ProcessPoolExecutor

from concurrent.futures import as_completed

def fib(n):

return 1 if n <= 2 else fib(n-1) + fib(n-2)

if __name__ == '__main__':

# with ProcessPoolExecutor(3) as executor:

with ThreadPoolExecutor(3) as executor:

all_task = [executor.submit(fib, n) for n in range(25, 35)]

start_time = time.time()

for future in as_completed(all_task):

data = future.result()

# todo

end_time = time.time()

print("time consuming by threads: {0}s".format(end_time-start_time))

# print("time consuming by processes: {0}s".format(end_time-start_time))

兩種方式的運(yùn)行結(jié)果對比:

# result:

# time consuming by threads: 4.823292016983032s

# time consuming by processes: 3.3890748023986816s

可以看到,對于高計(jì)算量的任務(wù)潮模,多進(jìn)程要比多線程更加高效亮蛔。同時(shí),從這個(gè)例子中還能看出擎厢,通過concurrent.futures模塊使用線程池進(jìn)程池的方式的接口和使用邏輯是一樣的究流,不過在使用多進(jìn)程時(shí),對于Windows的操作平臺动遭,相關(guān)邏輯一定要放在main中芬探,Linux不受約束。

2.用多進(jìn)程多線程兩種方式來模擬 I/O密集操作厘惦,I/O操作 的特點(diǎn)就是 cpu 要耗費(fèi)大量的時(shí)間進(jìn)行等待數(shù)據(jù)偷仿,這里用sleep()進(jìn)行模擬即可。

整體的操作方式不變宵蕉,修改過的邏輯如下:

def random_sleep(n):

time.sleep(n)

return n

...

# 8 個(gè)線程酝静,每個(gè)休眠兩秒,模擬 I/O

with ProcessPoolExecutor(8) as executor:

# with ThreadPoolExecutor(8) as executor:

all_task = [executor.submit(random_sleep, 2) for i in range(30)]

# result:

# time consuming by threads: 8.002903699874878s

# time consuming by processes: 8.34946894645691s

多進(jìn)程編程

直接使用

import time

import multiprocessing

def read(times):

time.sleep(times)

print("process reading...")

return "read for {0}s".format(times)

def write(times):

time.sleep(times)

print("process writing...")

return "write for {0}s".format(times)

if __name__ == '__main__':

read_process = multiprocessing.Process(target=read, args=(1,))

write_process = multiprocessing.Process(target=write, args=(2,))

read_process.start()

write_process.start()

print("read_process id {rid}".format(rid=read_process.pid))

print("write_process id {wid}".format(wid=write_process.pid))

read_process.join()

write_process.join()

print("done")

# result:

# read_process id 7064

# write_process id 836

# process reading...

# process writing...

# done

可以看出羡玛,關(guān)于多線程的邏輯和多線程的使用方式以類似的别智,要注意在Windows操作系統(tǒng)上,和進(jìn)程有關(guān)的邏輯要寫在if __name__ == '__main__'中稼稿。其他的一些方法請參閱 官方文檔薄榛。

使用原生進(jìn)程池

import time

import multiprocessing

def read(times):

time.sleep(times)

print("process reading...")

return "read for {0}s".format(times)

def write(times):

time.sleep(times)

print("process writing...")

return "write for {0}s".format(times)

if __name__ == '__main__':

# multiprocessing.cpu_count() 獲取cpu的核心數(shù)

pool = multiprocessing.Pool(multiprocessing.cpu_count())

read_result = pool.apply_async(read, args=(2,))

write_result = pool.apply_async(write, args=(3,))

# 關(guān)閉進(jìn)程池,不再接受新的任務(wù)提交让歼,否則 join() 出錯(cuò)

pool.close()

# 等待進(jìn)程池中提交的所有任務(wù)完成

pool.join()

print(read_result.get())

print(write_result.get())

# result:

# process reading...

# process writing...

# read for 2s

# write for 3s

使用imap()敞恋,所有任務(wù)順序執(zhí)行:

pool = multiprocessing.Pool(multiprocessing.cpu_count())

for result in pool.imap(read, [2, 1, 3]):

print(result)

# result:

# process reading...

# process reading...

# read for 2s

# read for 1s

# process reading...

# read for 3s

使用imap_unordered(),哪個(gè)任務(wù)先完成就先返回結(jié)果:

for result in pool.imap_unordered(read, [1, 5, 3]):

print(result)

# process reading...

# read for 1s

# process reading...

# read for 3s

# process reading...

# read for 5s

使用concurrent.futures中的ProcessPoolExecutor

這個(gè)在多線程和多進(jìn)程對比的時(shí)提到過是越,因?yàn)楹投嗑€程的使用方式一樣,這里就不多贅述碌上,可以參閱 官方文檔 給出的例子

進(jìn)程間通信

進(jìn)程通信和線程通信有些區(qū)別倚评,在線程通信中各種提供的鎖的機(jī)制全局變量在這里不再適用,我們要選取新的工具來完成進(jìn)程通信任務(wù)馏予。

使用multiprocessing.Queue

使用邏輯是和多線程中的Queue是一樣的天梧,詳細(xì)方法。這種通信方式不能用在通過Pool進(jìn)程池創(chuàng)建的進(jìn)程

import multiprocessing

import time

def plus(queue):

for i in range(6):

num = queue.get() + 1

queue.put(num)

print(num)

time.sleep(1)

def subtract(queue):

for i in range(6):

num = queue.get() - 1

queue.put(num)

print(num)

time.sleep(2)

if __name__ == '__main__':

queue = multiprocessing.Queue(1)

queue.put(0)

plus_process = multiprocessing.Process(target=plus, args=(queue,))

subtract_process = multiprocessing.Process(target=subtract, args=(queue,))

plus_process.start()

subtract_process.start()

# result:

# 1

# 1

# 2

# 2

# 3

# 3

# 0

# 1

# 2

# 2

# 1

# 0

使用Manager()中的Queue

Manager()會返回一個(gè)在進(jìn)程間進(jìn)行同步管理的一個(gè)對象霞丧,它提供了多種在進(jìn)程間共享數(shù)據(jù)的形式呢岗。

import multiprocessing

import time

def plus(queue):

for i in range(6):

num = queue.get() + 1

queue.put(num)

print(num)

time.sleep(1)

def subtract(queue):

for i in range(6):

num = queue.get() - 1

queue.put(num)

print(num)

time.sleep(2)

if __name__ == '__main__':

queue = multiprocessing.Manager().Queue(1) # 創(chuàng)建方式有些奇特

# queue = multiprocessing.Queue() # 這時(shí)用這個(gè)就行不通了

pool = multiprocessing.Pool(2)

queue.put(0)

pool.apply_async(plus, args=(queue,))

pool.apply_async(subtract, args=(queue,))

pool.close()

pool.join()

# result:

# 0

# 1

# 1

# 2

# 2

# 3

# -1

# 0

# 1

# 2

# 1

# 0

使用Manager()中的list()

多個(gè)進(jìn)程可以共享全局的list,因?yàn)槭沁M(jìn)程間共享,所以用鎖的機(jī)制保證它的安全性后豫。這里的Manager().Lock不是前面線程級別的Lock悉尾,它可以保證進(jìn)程間的同步。

import multiprocessing as mp

import time

def add_person(waiting_list, name_list, lock):

lock.acquire()

for name in name_list:

waiting_list.append(name)

time.sleep(1)

print(waiting_list)

lock.release()

def get_person(waiting_list, lock):

lock.acquire()

if waiting_list:

name = waiting_list.pop(0)

print("get {0}".format(name))

lock.release()

if __name__ == '__main__':

waiting_list = mp.Manager().list()

lock = mp.Manager().Lock() # 使用 lock 限制進(jìn)程對全局量的訪問

name_list = ["MetaTian", "Rity", "Anonymous"]

add_process = mp.Process(target=add_person, args=(waiting_list, name_list, lock))

get_process = mp.Process(target=get_person, args=(waiting_list, lock))

add_process.start()

get_process.start()

add_process.join()

get_process.join()

print(waiting_list)

# result:

# ['MetaTian']

# ['MetaTian', 'Rity']

# ['MetaTian', 'Rity', 'Anonymous']

# get MetaTian

# ['Rity', 'Anonymous']

Manager()中還有更多的進(jìn)程間通信的工具挫酿,可以參閱官方文檔构眯。

使用Pipe

Pipe只能適用于兩個(gè)進(jìn)程間的通信,它的性能高于Queue早龟,Pipe()會返回兩個(gè)Connection對象惫霸,使用這個(gè)對象可以在進(jìn)程間進(jìn)行數(shù)據(jù)的發(fā)送和接收,非常像前面講過的socket對象葱弟。關(guān)于Connection

import multiprocessing

def plus(conn):

default_num = 0

for i in range(3):

num = 0 if i == 0 else conn.recv()

conn.send(num + 1)

print("plus send: {0}".format(num+1))

def subtract(conn):

for i in range(3):

num = conn.recv()

conn.send(num-1)

print("subtract send: {0}".format(num-1))

if __name__ == '__main__':

conn_plus, conn_sbtract = multiprocessing.Pipe()

plus_process = multiprocessing.Process(target=plus, args=(conn_plus,))

subtract_process = multiprocessing.Process(target=subtract, args=(conn_sbtract,))

plus_process.start()

subtract_process.start()

# result:

# plus send: 1

# subtract send: 0

# plus send: 1

# subtract send: 0

# plus send: 1

# subtract send: 0

send()可以連續(xù)發(fā)送數(shù)據(jù)壹店,recv()將另一端發(fā)送的數(shù)據(jù)陸續(xù)取出,如果沒有取到數(shù)據(jù)芝加,則進(jìn)入等待狀態(tài)硅卢。

注:喜歡python + qun:839383765 可以獲取Python各類免費(fèi)最新入門學(xué)習(xí)資料!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末妖混,一起剝皮案震驚了整個(gè)濱河市老赤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌制市,老刑警劉巖抬旺,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異祥楣,居然都是意外死亡开财,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門误褪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來责鳍,“玉大人,你說我怎么就攤上這事兽间±穑” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵嘀略,是天一觀的道長恤溶。 經(jīng)常有香客問我,道長帜羊,這世上最難降的妖魔是什么咒程? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮讼育,結(jié)果婚禮上帐姻,老公的妹妹穿的比我還像新娘稠集。我一直安慰自己,他們只是感情好饥瓷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布剥纷。 她就那樣靜靜地躺著,像睡著了一般扛伍。 火紅的嫁衣襯著肌膚如雪筷畦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天刺洒,我揣著相機(jī)與錄音鳖宾,去河邊找鬼。 笑死逆航,一個(gè)胖子當(dāng)著我的面吹牛鼎文,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播因俐,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拇惋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了抹剩?” 一聲冷哼從身側(cè)響起撑帖,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎澳眷,沒想到半個(gè)月后胡嘿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钳踊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年衷敌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拓瞪。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缴罗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出祭埂,到底是詐尸還是另有隱情面氓,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布蛆橡,位于F島的核電站舌界,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏航罗。R本人自食惡果不足惜禀横,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一屁药、第九天 我趴在偏房一處隱蔽的房頂上張望粥血。 院中可真熱鬧柏锄,春花似錦、人聲如沸复亏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缔御。三九已至抬闷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間耕突,已是汗流浹背笤成。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留眷茁,地道東北人炕泳。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像上祈,于是被迫代替她去往敵國和親培遵。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

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