標籤:共用資料 也有 read dict 資料共用 注釋 sleep exec contex
python中的線程是假線程,不同線程之間的切換是需要耗費資源的,因為需要儲存線程的上下文,不斷的切換就會耗費資源。。
python多線程適合io操作密集型的任務(如socket server 網路並發這一類的);
python多線程不適合cpu密集操作型的任務,主要使用cpu來計算,如大量的數學計算。
那麼如果有cpu密集型的任務怎麼辦,可以通過多進程來操作(不是多線程)。
假如CPU有8核,每核CPU都可以用1個進程,每個進程可以用1個線程來進行計算。
進程之間不需要使用gil鎖,因為進程是獨立的,不會共用資料。
進程可以起很多個,但是8核CPU同時只能對8個任務進行操作。
多進程
#測試多進程import multiprocessingimport timedef run(name): time.sleep(2) print (‘heelo‘,name)if __name__ == ‘__main__‘: for i in range(10): #起了10個進程 p = multiprocessing.Process(target=run,args=(‘msc%s‘ %i,)) p.start()執行結果:hello msc1hello msc0hello msc2hello msc3hello msc5hello msc4hello msc6hello msc7hello msc8hello msc9
import multiprocessingimport time,threadingdef thread_run(): print (threading.get_ident()) #get_ident擷取當前線程iddef run(name): time.sleep(2) print (‘hello‘,name) t = threading.Thread(target=thread_run,) #在每個進程中又起了1個線程 t.start()if __name__ == ‘__main__‘: for i in range(10): #起了10個進程 p = multiprocessing.Process(target=run,args=(‘msc%s‘ %i,)) p.start()執行結果:hello msc013996hello msc214208hello msc113964hello msc314012hello msc615192hello msc715136hello msc87036hello msc412344hello msc915332hello msc513616
from multiprocessing import Processimport osdef info(title): print(title) print(‘module name:‘, __name__) print(‘parent process:‘, os.getppid()) #擷取父進程的id print(‘process id:‘, os.getpid()) #擷取自身的id print("\n\n")def f(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()執行結果:main process linemodule name: __main__parent process: 7172 #父進程就是python
process id: 14880 #這個子進程就是python的代碼程式
##每個進程都會有一個父進程。
from multiprocessing import Processimport osdef info(title): print(title) print(‘module name:‘, __name__) print(‘parent process:‘, os.getppid()) #擷取父進程的id print(‘process id:‘, os.getpid()) #擷取自身的id print("\n\n")def f(name): info(‘\033[31;1mcalled from child process function f\033[0m‘) print(‘hello‘, name)if __name__ == ‘__main__‘: info(‘\033[32;1mmain process line\033[0m‘) p = Process(target=f, args=(‘msc‘,)) #設定子進程 p.start() #啟動子進程 # p.join()執行結果:main process linemodule name: __main__parent process: 1136 #主進程pycharmprocess id: 14684 #子進程python代碼called from child process function fmodule name: __mp_main__parent process: 14684 #主進程python代碼(1136的子進程)process id: 15884 #python代碼(主進程14684)中的子進程的子15884## 每個進程都有主進程(父進程)hello msc進程間通訊
預設進程之間資料是不共用的,如果一定要實現互訪可以通過Queue來實現,這個Queue和線程中的Queue使用方法一樣,不過線程中的Queue只能線上程之間使用。
線程import queueimport threadingdef f(): q.put([42,None,‘heelo‘])if __name__ == ‘__main__‘: q = queue.Queue() p = threading.Thread(target=f,) p.start() print (q.get()) p.join()執行結果:[42, None, ‘heelo‘]## 通過子線程put進去資料,然後在主線程get出內容,表明線程之間資料是可以共用的。
import queuefrom multiprocessing import Processdef f(): q.put([66,None,‘hello‘]) #這裡的q屬於主進程if __name__ == ‘__main__‘: q = queue.Queue() #主進程起的q p = Process(target=f,) ## 在主進程中來定義子進程;如果在主進程中啟動了子進程,那麼主進程和子進程之間記憶體是獨立的。 ## 因為記憶體獨立,子進程p是無法訪問主進程def f()中的q的。 p.start() print (q.get()) p.join()執行結果:Process Process-1:Traceback (most recent call last): File "C:\Python35\lib\multiprocessing\process.py", line 249, in _bootstrap self.run() File "C:\Python35\lib\multiprocessing\process.py", line 93, in run self._target(*self._args, **self._kwargs) File "D:\Program Files (x86)\python\day31\test.py", line 7, in f q.put([66,None,‘hello‘]) #這裡的q屬於主進程NameError: name ‘q‘ is not defined##可以看到已經報錯,這是因為子進程不能訪問主進程的q
import queuefrom multiprocessing import Processdef f(qq): qq.put([66,None,‘hello‘])if __name__ == ‘__main__‘: q = queue.Queue() p = Process(target=f,args=(q,)) #將父進程q傳給子進程 p.start() print (q.get()) p.join()執行結果:Traceback (most recent call last): File "D:/Program Files (x86)/python/day31/test.py", line 13, in <module> p.start() File "C:\Python35\lib\multiprocessing\process.py", line 105, in start self._popen = self._Popen(self) File "C:\Python35\lib\multiprocessing\context.py", line 212, in _Popen return _default_context.get_context().Process._Popen(process_obj) File "C:\Python35\lib\multiprocessing\context.py", line 313, in _Popen return Popen(process_obj) File "C:\Python35\lib\multiprocessing\popen_spawn_win32.py", line 66, in __init__ reduction.dump(process_obj, to_child) File "C:\Python35\lib\multiprocessing\reduction.py", line 59, in dump ForkingPickler(file, protocol).dump(obj)TypeError: can‘t pickle _thread.lock objects## 這是因為我們將線程的q傳給另一個進程,這是不可以的,線程只屬於當前進程,不能傳給其他進程。## 如果想將q傳給子進程,那麼必須將進程q傳進去,而不是線程q。
from multiprocessing import Process,Queue##大寫的Queue是進程隊列; queue是線程隊列##大寫的Queue需要從multiprocessing匯入def f(qq): qq.put([66,None,‘hello‘])if __name__ == ‘__main__‘: q = Queue() p = Process(target=f,args=(q,)) #將父進程q傳給子進程 p.start() print (q.get()) #父進程去get子進程的內容 p.join()執行結果:[66, None, ‘hello‘]##父進程可以get子進程put進去的內容了;從表面上看感覺是兩個進程共用了資料,其實不然。‘‘‘
現在已經實現了進程間的通訊。父進程將q傳給子進程,其實是複製了一份q給子進程,此時子進程就多了一個q進程隊列;
但是父進程又為什麼能夠get子進程put進去的資料呢,這是因為當前兩個進程在記憶體空間依然是獨立的,只不過子進程put的資料 通過pickle序列化放到記憶體中一個中間的位置,然後父進程從這個中間的位置取到資料(而不是從子進程中取的資料)。
所以進程間的通訊不是共用資料,而是一個資料的傳遞。
‘‘‘
進程之間的資料還可以通過管道的方式來通訊from multiprocessing import Process, Pipedef f(conn): conn.send([66, None, ‘hello from child1‘]) #發送資料給parent_conn conn.close() #發完資料需要關閉if __name__ == ‘__main__‘: parent_conn, child_conn = Pipe() ## 產生管道。 產生時會產生兩個返回對象,這兩個對象相當於兩端的電話,通過管道線路串連。 ## 兩個對象分別交給兩個變數。 p = Process(target=f, args=(child_conn,)) #child_conn需要傳給對端,用於send資料給parent_conn p.start() print(parent_conn.recv()) #parent_conn在這端,用於recv資料 p.join()執行結果:[66, None, ‘hello from child1‘]
from multiprocessing import Process, Pipedef f(conn): conn.send([66, None, ‘hello from child1‘]) conn.send([66, None, ‘hello from child2‘]) #發送兩次資料 conn.close()if __name__ == ‘__main__‘: parent_conn, child_conn = Pipe() p = Process(target=f, args=(child_conn,)) p.start() print(parent_conn.recv()) p.join()執行結果:[66, None, ‘hello from child1‘]## 可以看到這端只接收到了一次資料
from multiprocessing import Process, Pipedef f(conn): conn.send([66, None, ‘hello from child1‘]) conn.send([66, None, ‘hello from child2‘]) conn.close()if __name__ == ‘__main__‘: parent_conn, child_conn = Pipe() p = Process(target=f, args=(child_conn,)) p.start() print(parent_conn.recv()) print(parent_conn.recv()) #第二次接收資料 p.join()執行結果:[66, None, ‘hello from child1‘][66, None, ‘hello from child2‘]##對端發送幾次,這端就需要接收幾次
from multiprocessing import Process, Pipedef f(conn): conn.send([66, None, ‘hello from child1‘]) conn.send([66, None, ‘hello from child2‘]) #發送兩次資料 conn.close()if __name__ == ‘__main__‘: parent_conn, child_conn = Pipe() p = Process(target=f, args=(child_conn,)) p.start() print(parent_conn.recv()) print(parent_conn.recv()) print(parent_conn.recv()) #對端發送兩次,本段接收三次 p.join()執行結果:[66, None, ‘hello from child1‘][66, None, ‘hello from child2‘]## 程式卡主了,除非對端在發送一次資料。
from multiprocessing import Process, Pipedef f(conn): conn.send([66, None, ‘hello from child1‘]) conn.send([66, None, ‘hello from child2‘]) #發送兩次資料 print (conn.recv()) #接收資料 conn.close()if __name__ == ‘__main__‘: parent_conn, child_conn = Pipe() p = Process(target=f, args=(child_conn,)) p.start() print(parent_conn.recv()) print(parent_conn.recv()) parent_conn.send("data from parent_conn") #發送資料 p.join()執行結果:[66, None, ‘hello from child1‘][66, None, ‘hello from child2‘]data from parent_conn##通過管道實現了相互發送接收資料(實現了資料傳遞)進程間資料互動及共用
from multiprocessing import Process, Managerimport osdef f(d, l, n): d[1] = ‘1‘ #放入key和value到空字典中 d[‘2‘] = 2 d[0.25] = None l.append(n) #將每個進程的n值放入列表中;每個進程的n值都不同。 print(l)if __name__ == ‘__main__‘: with Manager() as manager: #做一個別名,此時manager就相當於Manager() d = manager.dict() #產生一個可在多個進程之間傳遞和共用的字典 l = manager.list(range(5)) #產生一個可在多個進程之間傳遞和共用的列表;通過range(5)給列表中產生5個資料 p_list = [] for i in range(10): #產生10個進程 p = Process(target=f, args=(d, l, i)) #將字典和列表傳給每個進程,每個進程可以進行修改 p.start() p_list.append(p) # 將每個進程放入空列表中 for res in p_list: res.join() print(d) #所有進程都執行完畢後列印字典 print(l) #所有進程都執行完畢後列印列表執行結果:#列表產生的時候自動加入了0-4這5個數;然後每個進程又把各自的n值加入列表[0, 1, 2, 3, 4, 2][0, 1, 2, 3, 4, 2, 3][0, 1, 2, 3, 4, 2, 3, 4][0, 1, 2, 3, 4, 2, 3, 4, 1][0, 1, 2, 3, 4, 2, 3, 4, 1, 0][0, 1, 2, 3, 4, 2, 3, 4, 1, 0, 5][0, 1, 2, 3, 4, 2, 3, 4, 1, 0, 5, 6][0, 1, 2, 3, 4, 2, 3, 4, 1, 0, 5, 6, 7][0, 1, 2, 3, 4, 2, 3, 4, 1, 0, 5, 6, 7, 8][0, 1, 2, 3, 4, 2, 3, 4, 1, 0, 5, 6, 7, 8, 9] #第十個進程把每個進程添加的n值都加入到列表{0.25: None, 1: ‘1‘, ‘2‘: 2} #最後列印的字典[0, 1, 2, 3, 4, 2, 3, 4, 1, 0, 5, 6, 7, 8, 9] #最後列印的列表Process finished with exit code 0
from multiprocessing import Process, Managerimport osdef f(d, l): d[os.getpid()] = os.getpid() l.append(os.getpid()) print(l)if __name__ == ‘__main__‘: with Manager() as manager: d = manager.dict() #對字典做個調整,也將pid加入到字典中 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)執行結果:[0, 1, 2, 3, 4, 2240][0, 1, 2, 3, 4, 2240, 10152][0, 1, 2, 3, 4, 2240, 10152, 10408][0, 1, 2, 3, 4, 2240, 10152, 10408, 6312][0, 1, 2, 3, 4, 2240, 10152, 10408, 6312, 17156][0, 1, 2, 3, 4, 2240, 10152, 10408, 6312, 17156, 6184][0, 1, 2, 3, 4, 2240, 10152, 10408, 6312, 17156, 6184, 16168][0, 1, 2, 3, 4, 2240, 10152, 10408, 6312, 17156, 6184, 16168, 11384][0, 1, 2, 3, 4, 2240, 10152, 10408, 6312, 17156, 6184, 16168, 11384, 15976][0, 1, 2, 3, 4, 2240, 10152, 10408, 6312, 17156, 6184, 16168, 11384, 15976, 16532]{2240: 2240, 10152: 10152, 10408: 10408, 6312: 6312, 17156: 17156, 6184: 6184, 16168: 16168, 11384: 11384, 15976: 15976, 16532: 16532}[0, 1, 2, 3, 4, 2240, 10152, 10408, 6312, 17156, 6184, 16168, 11384, 15976, 16532]##現在我們看到可以實現進程間的資料共用、修改和傳遞。##Manager()內建鎖,會控制進程之間同一時間修改資料;##字典和列表的資料不是一份,而是因為10個進程,所以有10個字典和10個列表。每個進程修改後,都會copy給其他進程,其他進程可以對最新的資料進行修改,所以資料不會被修改亂。進程同步
在進程裡面也有鎖
from multiprocessing import Process, Lock #從multiprocessing匯入Lock這個鎖def f(l, i): l.acquire() #擷取修改資料的鎖 print(‘hello world‘, i) l.release() #釋放鎖if __name__ == ‘__main__‘: lock = Lock() #執行個體鎖 for num in range(10): #產生10個進程 Process(target=f, args=(lock, num)).start() #執行子進程並傳入參數給子進程執行結果:hello world 1hello world 4hello world 0hello world 3hello world 2hello world 5hello world 6hello world 8hello world 7hello world 9## 可以看到一共10個進程,並不是連續的,說明執行進程的時候說不準先執行哪個進程。‘‘‘
進程之間資料是獨立的,這裡我們為什麼又要加鎖呢,這是因為所有進程使用同一個螢幕來輸出資料;
比如 我們現在輸出的資料是 hello world x,在輸出的過程中很有可能其中一個進程還沒輸出完(比如只輸出了hello wo),另一個進程就執行輸出了(可能會在螢幕上看到hello wohello world0201的現象)。
所以需要通過鎖來控制同一時間只能有一個進程輸出資料到螢幕。
‘‘‘
進程池
執行多進程,子進程會從主進程複製一份完整資料,1個、10個進程可能還沒什麼感覺,但是如果有100或1000,甚至更多個進程的時候開銷就會特別大,就會明顯感覺到多進程執行有卡頓現象。
進程池可以設定同一時間有多少個進程可以在CPU上運行。
from multiprocessing import Process, Pool#從multiprocessing匯入poolimport time,osdef Foo(i): time.sleep(2) print("in process",os.getpid()) #列印進程id return i + 100def Bar(arg): print(‘-->exec done:‘, arg)if __name__ == ‘__main__‘: ##這行代碼用途是如果主動執行該代碼的.py檔案,則該代碼下面的代碼可以被執行;如果該.py模組被匯入到其他模組中,從其他模組執行該.py模組,則該行下面的代碼不會被執行。 有些時候可以用這種方式用於測試,在該行代碼下面寫一些測試代碼。。 pool = Pool(5) #同時只能放入5個進程 for i in range(10): #建立10個進程,但是因為pool的限制,只有放入進程池中的5個進程才會被執行(),其他的被掛起了,如果進程池中其中有兩個進程執行完了,就會補進2個進程進去。 # pool.apply_async(func=Foo, args=(i,), callback=Bar) pool.apply(func=Foo, args=(i,)) #pool.apply用來將進程放入pool print(‘end‘) #執行完畢 pool.close() #允許pool中的進程關閉(close必須在join前面,可以理解close相當於一個開關吧) pool.join() # 進程池中進程執行完畢後再關閉,如果注釋,那麼程式直接關閉。執行結果:in process 13616in process 10912in process 12472in process 15180in process 12404in process 13616in process 10912in process 12472in process 15180in process 12404end##可以看到通過串列的方式將結果列印出來,這是因為我們使用的是pool.apply。 pool.apply就是通過串列的方式來執行。
from multiprocessing import Process, Poolimport time,osdef Foo(i): time.sleep(2) print("in process",os.getpid()) return i + 100def Bar(arg): print(‘-->exec done:‘, arg)if __name__ == ‘__main__‘: pool = Pool(5) for i in range(10): pool.apply_async(func=Foo, args=(i,)) ## 使用pool.apply_async就可以並行了 print(‘end‘) pool.close() # pool.join() 注釋掉執行結果:end## 只執行了print(‘end‘)代碼,其他進程的結果沒有看到,這是因為其他進程還沒有執行完成,主進程pool.close()就執行完了,close以後所有其他進程也不會在執行了。## 要想其他進程執行完成後在關閉,必須使用pool.join()
from multiprocessing import Process, Poolimport time,osdef Foo(i): time.sleep(2) print("in process",os.getpid()) return i + 100def Bar(arg): print(‘-->exec done:‘, arg)if __name__ == ‘__main__‘: pool = Pool(5) for i in range(10): pool.apply_async(func=Foo, args=(i,)) print(‘end‘) pool.close() pool.join()執行結果:endin process 14756in process 14596in process 10836in process 12536in process 12904in process 14756in process 14596in process 10836in process 12536in process 12904##從執行結果來看,5個 5個的被列印出來。回調
from multiprocessing import Process, Poolimport time,osdef Foo(i): time.sleep(2) print("in process",os.getpid()) return i + 100def Bar(arg): print(‘-->exec done:‘, arg,os.getpid())if __name__ == ‘__main__‘: pool = Pool(5) print ("主進程:",os.getpid()) #列印主進程id for i in range(10): pool.apply_async(func=Foo, args=(i,),callback=Bar) ##callback叫做回調,就是當執行完了func=Foo後,才會執行callback=Bar(每個進程執行完了後都會執行回調)。 ## 回調可以用於當執行完代碼後做一些後續操作,比如查看完命令後,通過回調進行備份;或者執行完什麼動作後,做個日誌等。 ## 備份、寫日誌等在子進程中也可以執行,但是為什麼要用回調呢! 這是因為如果用子進程,有10個子進程就得串連資料庫十次,而使用回調的話是用主進程串連資料庫,所以只串連一次就可以了,這樣寫能大大提高運行效率。 ##通過主進程建立資料庫的串連的話,因為在同一個進程中只能在資料庫建立一次串連,所以即使是多次被子進程回調,也不會重複建立串連的,因為資料庫會限制同一個進程最大串連數,這都是有資料庫設定的。 print(‘end‘) pool.close() pool.join()執行結果:主進程: 14340 #主進程是 14340endin process 13936-->exec done: 100 14340 #可以看出回調是通過主線程調用的in process 15348-->exec done: 101 14340in process 10160-->exec done: 102 14340in process 11612-->exec done: 103 14340in process 14836-->exec done: 104 14340in process 13936-->exec done: 105 14340in process 15348-->exec done: 106 14340in process 10160-->exec done: 107 14340in process 11612-->exec done: 108 14340in process 14836-->exec done: 109 14340
文章根據 代碼老兵 的分享部落格,一點點搞出來的,多線程和進程讓我頭疼了三天,感謝大神們的分享的經驗,讓我少走彎路。
Python3 多線程、多進程