Python3 多線程、多進程

來源:互聯網
上載者:User

標籤:共用資料   也有   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 多線程、多進程

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.