深入理解Python 多線(xiàn)程
Python里的多線(xiàn)程是假的多線(xiàn)程,不管有多少核,同一時(shí)間只能在一個(gè)核中進(jìn)行操作!利用Python的多線(xiàn)程,只是利用CPU上下文切換的優(yōu)勢(shì),看上去像是并發(fā),其實(shí)只是個(gè)單線(xiàn)程,所以說(shuō)他是假的單線(xiàn)程。
那么什么時(shí)候用多線(xiàn)程呢?
首先要知道:
io操作不占用CPU 計(jì)算操作占CPU,像2+5=5Python的多線(xiàn)程不適合CPU密集操作型的任務(wù),適合io密集操作型的任務(wù),例如:SocketServer
如果現(xiàn)在再有CPU密集操作型的任務(wù),那該怎么辦呢?
首先說(shuō),多進(jìn)程的進(jìn)程之間是獨(dú)立的,然后注意了,python的線(xiàn)程用的是系統(tǒng)的原生線(xiàn)程,python的進(jìn)程也是用系統(tǒng)的原生進(jìn)程,那原生進(jìn)程是由操作系統(tǒng)維護(hù)的,說(shuō)白了python只是利用C原生代碼庫(kù)的接口?E嚓起了個(gè)進(jìn)程,真正的進(jìn)程管理還是由操作系統(tǒng)來(lái)完成的,那么操作系統(tǒng)本身有GIL全局解釋器鎖嗎?答案是沒(méi)有的,且兩個(gè)進(jìn)程之間的數(shù)據(jù)是完全獨(dú)立的,不能互相訪(fǎng)問(wèn),所以不需要鎖的概念,所以不存在GIL概念,所以在這種情況下,每個(gè)進(jìn)程至少會(huì)有一個(gè)線(xiàn)程,如果現(xiàn)在我的操作系統(tǒng)是八核的,我起八個(gè)進(jìn)程,然后每個(gè)進(jìn)程里面都有一個(gè)線(xiàn)程,那么就相當(dāng)于八線(xiàn)程了,八個(gè)線(xiàn)程跑在八核上,那么就相當(dāng)于利用多核了,那么問(wèn)題就解決了!
唯一的壞處是八個(gè)線(xiàn)程之間的數(shù)據(jù)是不能共享的,獨(dú)立的!利用這種方法可以折中的解決多核運(yùn)算的問(wèn)題!
先看一段簡(jiǎn)單的多進(jìn)程的程序:
import multiprocessingimport timedef run(name): time.sleep(2) print(’hello’, name)if __name__ == ’__main__’: for i in range(10): p = multiprocessing.Process(target=run, args=(’bob%s’%i,)) p.start()
程序的執(zhí)行結(jié)果為:
hello bob0hello bob1hello bob3hello bob2hello bob5hello bob9hello bob7hello bob8hello bob4hello bob6
那么,如果我想取我的進(jìn)程號(hào),那該怎么取呢?
from multiprocessing import Processimport osdef info(title): print(title) print(’module name:’, __name__) print(’parent process:’, os.getppid()) # 父進(jìn)程ID print(’process id:’, os.getpid()) # 自己進(jìn)程的ID print('nn')def f(name): info(’033[31;1mfunction f033[0m’) print(’hello’, name)if __name__ == ’__main__’: info(’033[32;1mmain process line033[0m’) p = Process(target=f, args=(’bob’,)) p.start() p.join()
程序執(zhí)行的結(jié)果為:
main process linemodule name: __main__parent process: 5252process id: 6576
function fmodule name: __mp_main__parent process: 6576process id: 2232
hello bob
其實(shí)這幅圖片的意思是,每一個(gè)子進(jìn)程都是由他父進(jìn)程啟動(dòng)的。
進(jìn)程間通訊
我們說(shuō)兩個(gè)進(jìn)程之間的內(nèi)存之間是相互獨(dú)立的,那么這兩個(gè)進(jìn)程能夠進(jìn)行通信嗎?說(shuō)A進(jìn)程向訪(fǎng)問(wèn)B進(jìn)程的數(shù)據(jù),能訪(fǎng)問(wèn)嗎?肯定是不可以訪(fǎng)問(wèn)的!但是,我就是想訪(fǎng)問(wèn),也就是兩個(gè)獨(dú)立的內(nèi)存想互相訪(fǎng)問(wèn),那該怎么辦呢?
有那么幾種方式,但是呢!萬(wàn)變不離其宗,也即是說(shuō)你必須找到一個(gè)中間件,有那么幾種中間件,那么先來(lái)看看是哪幾種
第一種Queues
使用方法跟threading里的queue差不多
from multiprocessing import Process, Queuedef f(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()
我們看這兩個(gè)進(jìn)程,父進(jìn)程的q是怎么傳給子進(jìn)程的?我們來(lái)討論一下
現(xiàn)在我們是不是認(rèn)為數(shù)據(jù)共享了,兩個(gè)進(jìn)程共享了一個(gè)q,其實(shí)不是的,其實(shí)是相當(dāng)于克隆了一個(gè)q,然后在父進(jìn)程里創(chuàng)建個(gè)子進(jìn)程,也就是父進(jìn)程把自己的q克隆了一份交給了子進(jìn)程,子進(jìn)程這個(gè)時(shí)候往這個(gè)q里面放了一份數(shù)據(jù),父進(jìn)程能夠獲取到 。那么這么說(shuō)就不對(duì)了,那克隆了一個(gè)q,也就是兩個(gè)q了,B往q里放了一個(gè)數(shù)據(jù),那么與另一個(gè)q,也就是A的q也就沒(méi)關(guān)系了,噯,按說(shuō)是這個(gè)樣子的,但是實(shí)際上呢,它是不是想實(shí)現(xiàn)個(gè)數(shù)據(jù)的共享啊,就相當(dāng)于把A這個(gè)q里的數(shù)據(jù)序列化了,序列化到了一個(gè)中間的位置,而中間位置有一個(gè)翻譯,他把這個(gè)數(shù)據(jù)反序列化給A,放在了A的q里,那么也就是實(shí)現(xiàn)了所謂的數(shù)據(jù)共享了。
程序執(zhí)行的結(jié)果為:
[42, None, ’hello’]
第二種Pipes
Pipe()函數(shù)返回一個(gè)由管道連接的連接對(duì)象,默認(rèn)情況下是雙工(雙向)。 例如:
from multiprocessing import Process, Pipedef f(conn): conn.send('父親,安好?') # 兒子發(fā) print('son receive:',conn.recv()) conn.close()if __name__ == ’__main__’: parent_conn, child_conn = Pipe() p = Process(target=f, args=(child_conn,)) p.start() print('father receive:',parent_conn.recv()) # 父親收 parent_conn.send('兒子,安好?') p.join()
程序執(zhí)行后的結(jié)果為:
father receive: 父親,安好?son receive: 兒子,安好?
Pipe()返回的兩個(gè)連接對(duì)象代表管道的兩端。 每個(gè)連接對(duì)象都有send()和recv()方法(以及其他方法)。 請(qǐng)注意,如果兩個(gè)進(jìn)程(或線(xiàn)程)同時(shí)嘗試讀取或?qū)懭牍艿赖耐欢耍瑒t管道中的數(shù)據(jù)可能會(huì)損壞。 當(dāng)然,同時(shí)使用管道的不同端部的過(guò)程不存在損壞的風(fēng)險(xiǎn)。
第三種Managers
Manager()返回的管理器對(duì)象控制一個(gè)服務(wù)器進(jìn)程,該進(jìn)程保存Python對(duì)象并允許其他進(jìn)程使用代理操作它們。
Manager()返回的管理器將支持類(lèi)型列表,dict,Namespace,Lock,RLock,Semaphore,BoundedSemaphore,Condition,Event,Barrier,Queue,Value和Array。 例如,
from multiprocessing import Process, Managerimport osdef f(d, l): d[1] = ’1’ d[’2’] = 2 d[0.25] = None l.append(os.getpid()) print(l)if __name__ == ’__main__’: with Manager() as manager: d = manager.dict() # 用專(zhuān)門(mén)的語(yǔ)法生成一個(gè)可在多個(gè)進(jìn)程之間進(jìn)行傳遞和共享的一個(gè)字典 l = manager.list(range(5)) # # 用專(zhuān)門(mén)的語(yǔ)法生成一個(gè)可在多個(gè)進(jìn)程之間進(jìn)行傳遞和共享的一個(gè)列表,默認(rèn)里有5個(gè)數(shù)據(jù) 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)
程序執(zhí)行的結(jié)果為:
[0, 1, 2, 3, 4, 2100][0, 1, 2, 3, 4, 2100, 7632][0, 1, 2, 3, 4, 2100, 7632, 5788][0, 1, 2, 3, 4, 2100, 7632, 5788, 6340][0, 1, 2, 3, 4, 2100, 7632, 5788, 6340, 5760][0, 1, 2, 3, 4, 2100, 7632, 5788, 6340, 5760, 7072][0, 1, 2, 3, 4, 2100, 7632, 5788, 6340, 5760, 7072, 7540][0, 1, 2, 3, 4, 2100, 7632, 5788, 6340, 5760, 7072, 7540, 3904][0, 1, 2, 3, 4, 2100, 7632, 5788, 6340, 5760, 7072, 7540, 3904, 7888][0, 1, 2, 3, 4, 2100, 7632, 5788, 6340, 5760, 7072, 7540, 3904, 7888, 7612]{1: ’1’, ’2’: 2, 0.25: None}[0, 1, 2, 3, 4, 2100, 7632, 5788, 6340, 5760, 7072, 7540, 3904, 7888, 7612]
進(jìn)程鎖與進(jìn)程池
進(jìn)程鎖
進(jìn)程也有一個(gè)鎖,what?進(jìn)程不都獨(dú)立了嗎?不涉及同時(shí)修改同一個(gè)數(shù)據(jù),怎么還會(huì)有鎖呢?
閑了來(lái)看看它的表現(xiàn)形式,幾乎和線(xiàn)程是一模一樣的
from multiprocessing import Process, Lockdef f(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()
程序執(zhí)行的結(jié)果為:
hello world 3hello world 1hello world 2hello world 5hello world 7hello world 4hello world 0hello world 6hello world 8hello world 9
那這種鎖有什么作用呢?
作用其實(shí)就是防止打印在屏幕上的信息發(fā)生錯(cuò)亂現(xiàn)象!
進(jìn)程池
在上面的程序中,啟動(dòng)100個(gè)進(jìn)程會(huì)發(fā)現(xiàn)變慢了,因?yàn)槠鹨粋€(gè)進(jìn)程就相當(dāng)克隆了一份父進(jìn)程的內(nèi)存數(shù)據(jù),如果父進(jìn)程占一個(gè)G的內(nèi)存空間,那我起100個(gè)進(jìn)程,就相當(dāng)于101G了,在這種情況下,開(kāi)銷(xiāo)是非常大的,就像起一個(gè)進(jìn)程?E嚓又克隆了一個(gè)屋子,一會(huì)就把哈爾濱占滿(mǎn)了,所以開(kāi)銷(xiāo)特別大,為了避免?E嚓起那么多的進(jìn)程,把系統(tǒng)打趴下,所以這里有個(gè)進(jìn)程池的限制。
進(jìn)程池就是同一時(shí)間有多少進(jìn)程在CPU運(yùn)行。
進(jìn)程池中有兩個(gè)方法:
apply(同步執(zhí)行,串行) apply_async(異步執(zhí)行、并行)from multiprocessing import Process,Pool,freeze_supportimport timeimport osdef Foo(i): time.sleep(2) print('in process',os.getpid()) return i+100def Bar(arg): print(’-->exec done:’,arg)if __name__ == ’__main__’: freeze_support() pool = Pool(5) # 允許進(jìn)程池里同時(shí)放入5個(gè)進(jìn)程 for i in range(10): # pool.apply_async(func=Foo, args=(i,),callback=Bar) # callback 回調(diào) pool.apply(func=Foo, args=(i,)) # 串行 # pool.apply_async(func=Foo, args=(i,)) # 并行 print(’end’) pool.close() pool.join() # 進(jìn)程池中進(jìn)程執(zhí)行完畢后再關(guān)閉,如果注釋?zhuān)敲闯绦蛑苯雨P(guān)閉。
程序的執(zhí)行結(jié)果為:
in process 7824in process 6540in process 7724in process 8924in process 9108in process 7824in process 6540
知識(shí)點(diǎn)擴(kuò)充:
__name__ == ’__main__’的作用是:
手動(dòng)執(zhí)行關(guān)于這段代碼的程序,那么他下面的程序就會(huì)執(zhí)行,如果是調(diào)用這段代碼的程序時(shí),那么它下面的程序就不會(huì)執(zhí)行
以上就是深入理解Python 多線(xiàn)程的詳細(xì)內(nèi)容,更多關(guān)于Python 多線(xiàn)程的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!
相關(guān)文章:
1. Spring security 自定義過(guò)濾器實(shí)現(xiàn)Json參數(shù)傳遞并兼容表單參數(shù)(實(shí)例代碼)2. Java8內(nèi)存模型PermGen Metaspace實(shí)例解析3. python tkinter實(shí)現(xiàn)下載進(jìn)度條及抖音視頻去水印原理4. 一文搞懂 parseInt()函數(shù)異常行為5. 聊聊python在linux下與windows下導(dǎo)入模塊的區(qū)別說(shuō)明6. python學(xué)習(xí)之plot函數(shù)的使用教程7. python 實(shí)現(xiàn)"神經(jīng)衰弱"翻牌游戲8. ASP.NET MVC使用正則表達(dá)式驗(yàn)證手機(jī)號(hào)碼9. Python基于百度AI實(shí)現(xiàn)抓取表情包10. python中用Scrapy實(shí)現(xiàn)定時(shí)爬蟲(chóng)的實(shí)例講解
