5 多進(jìn)程
gitbook鏈接:用python帶你進(jìn)入AI中的深度學(xué)習(xí)技術(shù)領(lǐng)域https://www.gitbook.com/book/scrappyzhang/python_to_deeplearn/details
github鏈接:https://github.com/ScrappyZhang/python_web_Crawler_DA_ML_DL
5.1 進(jìn)程
程序:例如xxx.py這是程序,是一個(gè)靜態(tài)的
進(jìn)程:一個(gè)程序運(yùn)行起來(lái)后,代碼+用到的資源 稱(chēng)之為進(jìn)程,它是操作系統(tǒng)分配資源的基本單元。
進(jìn)程的狀態(tài)
工作中,任務(wù)數(shù)往往大于cpu的核數(shù),即一定有一些任務(wù)正在執(zhí)行,而另外一些任務(wù)在等待cpu進(jìn)行執(zhí)行,因此導(dǎo)致了有了不同的狀態(tài)
- 就緒態(tài):運(yùn)行的條件都已經(jīng)慢去,正在等在cpu執(zhí)行
- 執(zhí)行態(tài):cpu正在執(zhí)行其功能
- 等待態(tài):等待某些條件滿足,例如一個(gè)程序sleep了,此時(shí)就處于等待態(tài)
5.2 python實(shí)現(xiàn)多進(jìn)程
我們來(lái)試想一下,在一個(gè)演唱會(huì)上,歌手歌唱、舞者翩翩起舞,各司其職才能達(dá)到較好的效果。我們可以這么理解:演唱會(huì)這個(gè)程序中有幾個(gè)進(jìn)程,一個(gè)是歌手歌唱,一個(gè)是舞者翩翩起舞,還有一個(gè)總協(xié)調(diào)的劇本。接下來(lái)我們就用python來(lái)實(shí)現(xiàn)這個(gè)想法:一個(gè)python程序有兩個(gè)子進(jìn)程:歌手歌唱、舞者翩翩起舞。
python中的multiprocessing模塊就是跨平臺(tái)版本的多進(jìn)程模塊,提供了一個(gè)Process類(lèi)來(lái)代表一個(gè)進(jìn)程對(duì)象,這個(gè)對(duì)象可以理解為是一個(gè)獨(dú)立的進(jìn)程,可以執(zhí)行另外的事情。Process的語(yǔ)法結(jié)構(gòu)如下:
Process([group [, target [, name [, args [, kwargs]]]]])
- target:如果傳遞了函數(shù)的引用,可以任務(wù)這個(gè)子進(jìn)程就執(zhí)行這里的代碼
- args:給target指定的函數(shù)傳遞的參數(shù),以元組的方式傳遞
- kwargs:給target指定的函數(shù)傳遞命名參數(shù)
- name:給進(jìn)程設(shè)定一個(gè)名字,可以不設(shè)定
- group:指定進(jìn)程組,大多數(shù)情況下用不到
Process創(chuàng)建的實(shí)例對(duì)象的常用方法:
- start():?jiǎn)?dòng)子進(jìn)程實(shí)例(創(chuàng)建子進(jìn)程)
- is_alive():判斷進(jìn)程子進(jìn)程是否還在活著
- join([timeout]):是否等待子進(jìn)程執(zhí)行結(jié)束,或等待多少秒
- terminate():不管任務(wù)是否完成,立即終止子進(jìn)程
Process創(chuàng)建的實(shí)例對(duì)象的常用屬性:
- name:當(dāng)前進(jìn)程的別名,默認(rèn)為Process-N,N為從1開(kāi)始遞增的整數(shù)
- pid:當(dāng)前進(jìn)程的pid(進(jìn)程號(hào))
我們來(lái)實(shí)現(xiàn)多進(jìn)程完成唱歌跳舞吧:
'''net04_sing_dance_multiprocess.py'''
import multiprocessing
import time
def sing():
for i in range(5):
print('正在唱歌呢 %d' % i)
time.sleep(1) # 休息1秒
def dance():
for i in range(5):
print('正在跳舞呢 %d' % i)
time.sleep(1) # 休息1秒
if __name__ == '__main__':
p1 = multiprocessing.Process(target=sing) # 創(chuàng)建唱歌進(jìn)程
p2 = multiprocessing.Process(target=dance) # 創(chuàng)建跳舞進(jìn)程
p1.start() # 開(kāi)始運(yùn)行進(jìn)程sing
p2.start() # 開(kāi)始運(yùn)行進(jìn)程dance
我們通過(guò)Process分別創(chuàng)建了唱歌子進(jìn)程p1和跳舞子進(jìn)程p2,然后啟動(dòng)使用start()了子進(jìn)程 。從結(jié)果中可以看到唱歌和跳舞完全互不影響,各自進(jìn)行直到結(jié)束。
當(dāng)然有經(jīng)驗(yàn)的讀者會(huì)說(shuō),這和多線程、協(xié)程(后續(xù)兩章)效果不一樣么,答曰:不一樣。python的os模塊中有關(guān)getpid方法可以獲取程序的進(jìn)程號(hào),我們通過(guò)在程序中添加os.getpid()來(lái)查看我們的程序有哪些進(jìn)程。在剛開(kāi)始運(yùn)行程序時(shí),我們可以在終端(MAC)輸入ps aux|grep net04
查看我們的程序在運(yùn)行時(shí)的進(jìn)程號(hào),然后進(jìn)行對(duì)比確認(rèn)。完整的代碼如下:
'''net04_getpid.py'''
import multiprocessing
import os
import time
def sing():
print('唱歌進(jìn)程pid: %d' % os.getpid())
for i in range(5):
print('正在唱歌呢 %d' % i)
time.sleep(1) # 休息1秒
def dance():
print('跳舞進(jìn)程pid: %d' % os.getpid())
for i in range(5):
print('正在跳舞呢 %d' % i)
time.sleep(1) # 休息1秒
if __name__ == '__main__':
print('父進(jìn)程pid: %d' % os.getpid())
p1 = multiprocessing.Process(target=sing) # 創(chuàng)建唱歌進(jìn)程
p2 = multiprocessing.Process(target=dance) # 創(chuàng)建跳舞進(jìn)程
p1.start() # 開(kāi)始運(yùn)行進(jìn)程sing
p2.start() # 開(kāi)始運(yùn)行進(jìn)程dance
從結(jié)果中可以看到,我們的程序運(yùn)行時(shí)共占用了三個(gè)進(jìn)程:19277、19278、19279,和我們預(yù)想的一樣。由于進(jìn)程時(shí)系統(tǒng)分配資源的基本單元,所以我們的三個(gè)進(jìn)程程序相比單進(jìn)程程序會(huì)占用系統(tǒng)更多的資源去同步完成唱歌和跳舞的任務(wù)。
既然我們實(shí)現(xiàn)了多進(jìn)程完成唱歌跳舞,那我們更進(jìn)一步的實(shí)現(xiàn)告訴子進(jìn)程讓誰(shuí)唱歌讓誰(shuí)跳舞吧,沒(méi)錯(cuò),我們就是導(dǎo)演,我們說(shuō)了算。通過(guò)在Process對(duì)象創(chuàng)建時(shí)通過(guò)對(duì)args或者kwargs進(jìn)行賦值來(lái)傳遞參數(shù)就可以實(shí)現(xiàn)。
singer = 'Jam'
sing_name = '不露聲色'
p1 = multiprocessing.Process(target=sing, args=(singer, sing_name)) # 創(chuàng)建唱歌進(jìn)程,告訴子進(jìn)程是Jam唱不露聲色
p2 = multiprocessing.Process(target=dance,kwargs={'dancer':'杰克遜'}) # 創(chuàng)建跳舞進(jìn)程,告訴子進(jìn)程是杰克遜來(lái)唱歌啦
實(shí)例完整代碼如下:
'''net04_sing_dance_variable.py'''
import multiprocessing
import time
def sing(name, sing_name):
for i in range(5):
print(name, '正在唱歌%s呢 %d' % (sing_name, i))
time.sleep(1) # 休息1秒
def dance(**kwargs):
dancer = kwargs['dancer']
for i in range(5):
print('%s正在伴舞呢 %d' % (dancer,i))
time.sleep(1) # 休息1秒
if __name__ == '__main__':
singer = 'Jam'
sing_name = '不露聲色'
p1 = multiprocessing.Process(target=sing, args=(singer, sing_name)) # 創(chuàng)建唱歌進(jìn)程,告訴子進(jìn)程是Jam唱不露聲色
p2 = multiprocessing.Process(target=dance,kwargs={'dancer':'杰克遜'}) # 創(chuàng)建跳舞進(jìn)程,告訴子進(jìn)程是杰克遜來(lái)唱歌啦
p1.start() # 開(kāi)始運(yùn)行進(jìn)程sing
p2.start() # 開(kāi)始運(yùn)行進(jìn)程dance
從運(yùn)行結(jié)果中我們可以很清晰的看到Jam唱不露聲色的時(shí)候杰克遜在為她伴舞——so crazy。
5.3 進(jìn)程間不共享全局變量
上一章我們知道線程間會(huì)共享全局變量,本節(jié)我們來(lái)想一下,進(jìn)程間呢?當(dāng)然不會(huì)啦,假如我們的電腦上裝有QQ音樂(lè)和網(wǎng)易云音樂(lè),在網(wǎng)易云音樂(lè)收藏的歌單不會(huì)自動(dòng)到QQ音樂(lè)的。我們一起看個(gè)實(shí)例吧。定義一個(gè)全局變量global_num=0,然后在唱歌和跳舞兩個(gè)代碼塊中添加global_num+1操作。我們會(huì)發(fā)現(xiàn),父進(jìn)程、sing子進(jìn)程和dance子進(jìn)程之間global_num并無(wú)半點(diǎn)關(guān)系,即進(jìn)程間不共享全局變量。
'''net04_global_variables.py'''
import multiprocessing
import time
global_num = 0
def sing():
global global_num
print('開(kāi)始:全局變量sing global_num= ', global_num)
for i in range(5):
print('正在唱歌呢 %d' % i)
global_num = global_num + 1 # 修改全局變量
time.sleep(1) # 休息1秒
print('結(jié)束:全局變量sing global_num= ', global_num)
def dance():
global global_num
print('開(kāi)始:全局變量dance global_num= ', global_num)
for i in range(5):
print('正在跳舞呢 %d' % i)
global_num = global_num + 1 # 修改全局變量
time.sleep(1) # 休息1秒
print('結(jié)束:全局變量dance global_num= ', global_num)
if __name__ == '__main__':
print('開(kāi)始:全局變量main global_num= ', global_num)
p1 = multiprocessing.Process(target=sing) # 創(chuàng)建唱歌進(jìn)程
p2 = multiprocessing.Process(target=dance) # 創(chuàng)建跳舞進(jìn)程
p1.start()
p2.start()
p1.join() # 待子進(jìn)程p1執(zhí)行完畢后再執(zhí)行下面的語(yǔ)句
p2.join() # 待子進(jìn)程p2執(zhí)行完畢后再執(zhí)行下面的語(yǔ)句
print('結(jié)束:全局變量main global_num= ', global_num)
5.4 進(jìn)程間通信
上一節(jié)我們了解到進(jìn)程間不共享全局變量,但是試想一下,我們電腦上登錄了兩個(gè)QQ并需要互傳消息或者杰克遜作為伴舞者打算給Jam遞花,怎么辦呢?這就要涉及進(jìn)程間通信。在多進(jìn)程程序中,有眾多的進(jìn)程間通信方式,我們今天通過(guò)python中multiprocessing模塊中的Queue來(lái)實(shí)現(xiàn)進(jìn)程間的通信。它的基本語(yǔ)法如下:
初始化Queue()對(duì)象時(shí)(例如:q=Queue()),若括號(hào)中沒(méi)有指定最大可接收的消息數(shù)量,或數(shù)量為負(fù)值,那么就代表可接受的消息數(shù)量沒(méi)有上限(直到內(nèi)存的盡頭);
- Queue.qsize():返回當(dāng)前隊(duì)列包含的消息數(shù)量;
- Queue.empty():如果隊(duì)列為空,返回True,反之False ;
- Queue.full():如果隊(duì)列滿了,返回True,反之False;
- Queue.get([block[, timeout]]):獲取隊(duì)列中的一條消息,然后將其從列隊(duì)中移除,block默認(rèn)值為T(mén)rue;
1)如果block使用默認(rèn)值,且沒(méi)有設(shè)置timeout(單位秒),消息列隊(duì)如果為空,此時(shí)程序?qū)⒈蛔枞ㄍT谧x取狀態(tài)),直到從消息列隊(duì)讀到消息為止,如果設(shè)置了timeout,則會(huì)等待timeout秒,若還沒(méi)讀取到任何消息,則拋出"Queue.Empty"異常;
2)如果block值為False,消息列隊(duì)如果為空,則會(huì)立刻拋出"Queue.Empty"異常;
- Queue.get_nowait():相當(dāng)Queue.get(False);
- Queue.put(item,[block[, timeout]]):將item消息寫(xiě)入隊(duì)列,block默認(rèn)值為T(mén)rue;
1)如果block使用默認(rèn)值,且沒(méi)有設(shè)置timeout(單位秒),消息列隊(duì)如果已經(jīng)沒(méi)有空間可寫(xiě)入,此時(shí)程序?qū)⒈蛔枞ㄍT趯?xiě)入狀態(tài)),直到從消息列隊(duì)騰出空間為止,如果設(shè)置了timeout,則會(huì)等待timeout秒,若還沒(méi)空間,則拋出"Queue.Full"異常;
2)如果block值為False,消息列隊(duì)如果沒(méi)有空間可寫(xiě)入,則會(huì)立刻拋出"Queue.Full"異常;
- Queue.put_nowait(item):相當(dāng)Queue.put(item, False)。
我們來(lái)看一下下面這個(gè)例子:
'''net04_queue_sample.py'''
from multiprocessing import Queue
q = Queue(3) # 初始化一個(gè)Queue對(duì)象,最多可接收三條put消息
q.put("消息1")
q.put("消息2")
print(q.full()) # False
q.put("消息3")
print(q.full()) # True
# 因?yàn)橄⒘嘘?duì)已滿,所以下面的try都會(huì)拋出異常,第一個(gè)try會(huì)等待2秒后再拋出異常,第二個(gè)Try會(huì)立刻拋出異常
try:
q.put("消息4", True, 2)
except:
print("消息列隊(duì)已滿,現(xiàn)有消息數(shù)量:%s" % q.qsize()) # mac os不支持qsize
# Note that this may raise NotImplementedError on Unix platforms like Mac OS X where sem_getvalue() is not implemented.
try:
q.put_nowait("消息4")
except:
print("消息列隊(duì)已滿,現(xiàn)有消息數(shù)量:%s" % q.qsize())
# 推薦的方式,先判斷消息列隊(duì)是否已滿,再寫(xiě)入
if not q.full():
q.put_nowait("消息4")
# 讀取消息時(shí),先判斷消息列隊(duì)是否為空,再讀取
if not q.empty():
for i in range(q.qsize()):
print(q.get_nowait())
運(yùn)行結(jié)果與語(yǔ)法中定義的一樣,put為寫(xiě)入,get為讀出;full為判斷是否寫(xiě)滿等。
False
True
消息列隊(duì)已滿,現(xiàn)有消息數(shù)量:3
消息列隊(duì)已滿,現(xiàn)有消息數(shù)量:3
消息1
消息2
消息3
接下來(lái)我們就通過(guò)multiprocessing模塊中的Queue來(lái)實(shí)現(xiàn)唱歌跳舞時(shí)杰克遜為Jam遞花這一構(gòu)想。具體代碼如下:
'''net04_sing_dance_queue.py'''
import multiprocessing
import time
def sing(name, sing_name):
for i in range(5):
print(name, '正在唱歌%s呢 %d' % (sing_name, i))
time.sleep(1) # 休息1秒
while True:
if not q.empty():
value = q.get() # 從隊(duì)列中讀取數(shù)據(jù)
print('Jam收到了', value)
else:
break
def dance(**kwargs):
dancer = kwargs['dancer']
q.put('花') # 向隊(duì)列中寫(xiě)入花數(shù)據(jù)
print('杰克遜向Jam遞了一朵花')
for i in range(5):
print('%s正在伴舞呢 %d' % (dancer, i))
time.sleep(1) # 休息1秒
if __name__ == '__main__':
singer = 'Jam'
sing_name = '不露聲色'
q = multiprocessing.Queue() # 創(chuàng)建隊(duì)列
p1 = multiprocessing.Process(target=sing, args=(singer, sing_name)) # 創(chuàng)建唱歌進(jìn)程
p2 = multiprocessing.Process(target=dance, kwargs={'dancer': '杰克遜'}) # 創(chuàng)建跳舞進(jìn)程
p1.start() # 開(kāi)始運(yùn)行進(jìn)程sing
p2.start() # 開(kāi)始運(yùn)行進(jìn)程dance
在這個(gè)構(gòu)想的代碼中,我們首先創(chuàng)建了一個(gè)消息隊(duì)列Queue的實(shí)例對(duì)象q,然后在sing代碼塊中添加從隊(duì)列中讀取數(shù)據(jù)的語(yǔ)句q.get()代表收取禮物、在dance代碼塊中添加向隊(duì)列中寫(xiě)入數(shù)據(jù)的語(yǔ)句q.put(‘花’)代表送出了花。這樣便實(shí)現(xiàn)了舞者和歌手兩個(gè)進(jìn)程之間的通信。從代碼執(zhí)行的結(jié)果來(lái)看,確實(shí)如此。
5.5 進(jìn)程池Pool
在之前的進(jìn)程創(chuàng)建過(guò)程中我們通過(guò)multiprocessing中的Process來(lái)生成了唱歌和跳舞進(jìn)程,但如果我們這次演唱會(huì)請(qǐng)的是少女時(shí)代組合呢?那可是有好多人呢,一個(gè)一個(gè)創(chuàng)建太累了,有沒(méi)有批量創(chuàng)建的方法呢?答曰:有。我們可以使用進(jìn)程池的概念來(lái)批量創(chuàng)建進(jìn)程,即multiprocessing模塊中的Pool。
multiprocessing.Pool常用的函數(shù)介紹:
- apply_async(func[, args[, kwds]]) :使用非阻塞方式調(diào)用func(并行執(zhí)行,堵塞方式必須等待上一個(gè)進(jìn)程退出才能執(zhí)行下一個(gè)進(jìn)程),args為傳遞給func的參數(shù)列表,kwds為傳遞給func的關(guān)鍵字參數(shù)列表;
- close():關(guān)閉Pool,使其不再接受新的任務(wù);
- terminate():不管任務(wù)是否完成,立即終止;
- join():主進(jìn)程阻塞,等待子進(jìn)程的退出, 必須在close或terminate之后使用;
了解了基本語(yǔ)法,我們就來(lái)批量創(chuàng)建幾個(gè)歌手進(jìn)程吧:
首先我們創(chuàng)建一個(gè)最大只有3個(gè)進(jìn)程的進(jìn)程池:
processes = multiprocessing.Pool(3)
其次我們用進(jìn)程池中的apply_async來(lái)創(chuàng)建五個(gè)歌手進(jìn)程。
'''net04_process_pool.py'''
import multiprocessing
import time
def sing(singer_num, sleep_time):
for i in range(4):
print('歌手', singer_num, '正在唱歌呢 %d' % i)
time.sleep(sleep_time) # 休息
if __name__ == '__main__':
processes = multiprocessing.Pool(3) # 創(chuàng)建進(jìn)程池,最大進(jìn)程數(shù)為3
for i in range(5):
processes.apply_async(sing, (i + 1, 1 + 0.3 * i)) # 進(jìn)程池創(chuàng)建進(jìn)程 ,傳入?yún)?shù)為歌手編號(hào)和歌唱間隔休息時(shí)間
print('歌唱開(kāi)始')
processes.close()
processes.join()
print('歌唱結(jié)束')
由于我們創(chuàng)建的進(jìn)程池最大只能容納3個(gè)進(jìn)程,所以本實(shí)例中的4號(hào)歌手和5號(hào)歌手進(jìn)程需要等到之前幾位歌手進(jìn)程中的某一個(gè)執(zhí)行完畢方可創(chuàng)建。具體見(jiàn)結(jié)果。
注意:如果要使用Pool進(jìn)程池創(chuàng)建進(jìn)程,就需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue()。
5.6 進(jìn)程與線程對(duì)比
功能 {#功能}
- 進(jìn)程,能夠完成多任務(wù),比如 在一臺(tái)電腦上能夠同時(shí)運(yùn)行多個(gè)QQ
- 線程,能夠完成多任務(wù),比如 一個(gè)QQ中的多個(gè)聊天窗口
定義的不同 {#定義的不同}
進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位.
線程是進(jìn)程的一個(gè)實(shí)體,是CPU調(diào)度和分派的基本單位,它是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位.線程自己基本上不擁有系統(tǒng)資源,只擁有一點(diǎn)在運(yùn)行中必不可少的資源(如程序計(jì)數(shù)器,一組寄存器和棧),但是它可與同屬一個(gè)進(jìn)程的其他的線程共享進(jìn)程所擁有的全部資源.
區(qū)別 {#區(qū)別}
- 一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程.
- 線程的劃分尺度小于進(jìn)程(資源比進(jìn)程少),使得多線程程序的并發(fā)性高。
- 進(jìn)程在執(zhí)行過(guò)程中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線程共享內(nèi)存,從而極大地提高了程序的運(yùn)行效率
- 線線程不能夠獨(dú)立執(zhí)行,必須依存在進(jìn)程中
優(yōu)缺點(diǎn) {#優(yōu)缺點(diǎn)}
線程和進(jìn)程在使用上各有優(yōu)缺點(diǎn):線程執(zhí)行開(kāi)銷(xiāo)小,但不利于資源的管理和保護(hù);而進(jìn)程正相反。
5.7 多進(jìn)程實(shí)現(xiàn)文件拷貝
需求實(shí)現(xiàn):
通過(guò)多進(jìn)程的方式實(shí)現(xiàn)拷貝文件夾下的文件(文件夾嵌套不考慮);在拷貝過(guò)程中顯示拷貝進(jìn)度。
根據(jù)流程圖書(shū)寫(xiě)代碼要點(diǎn)
根據(jù)需求,我們可以將代碼劃分為兩大部分:主程序、拷貝函數(shù)。主程序用來(lái)創(chuàng)建每個(gè)文件拷貝操作的子進(jìn)程來(lái)實(shí)現(xiàn)多進(jìn)程拷貝。通過(guò)os模塊的listdir獲取到要拷貝的文件夾內(nèi)的文件名列表,并以此來(lái)創(chuàng)建相應(yīng)的子進(jìn)程,每個(gè)文件一個(gè)進(jìn)程。最后我們通過(guò)進(jìn)程間通信來(lái)讓每個(gè)子進(jìn)程在完成拷貝任務(wù)后來(lái)告知父進(jìn)程它完成了拷貝,并傳回它所拷貝的文件的文件名。父進(jìn)程通過(guò)獲取子進(jìn)程的消息并計(jì)算已經(jīng)完成的文件拷貝數(shù)來(lái)計(jì)算進(jìn)度并顯示。待100%時(shí)結(jié)束程序。
拷貝函數(shù)
文件拷貝:文件讀寫(xiě)操作
open(filename, 'rb')
f.read()
open(filename, 'wb')
f.write()
.close()
與父進(jìn)程通信
queue.put()
主程序
獲取文件列表
os.listdir(path)
多進(jìn)程創(chuàng)建
multiprocessing.Process(拷貝函數(shù))
獲取進(jìn)度
創(chuàng)建隊(duì)列
multiprocessing.Queue()
獲取子進(jìn)程通信內(nèi)容
queue.get()
完整代碼
'''net04_file_copy.py'''
import multiprocessing
import os
import time
import random
def copy_file(file_name, src, dest, queue):
'''拷貝文件的函數(shù)'''
src_file = open(src + '/' + file_name, 'rb')
dest_file = open(dest + '/' + file_name, 'wb')
while True:
time.sleep(random.random())
data = src_file.read(4096)
if data:
dest_file.write(data)
else:
break
src_file.close()
dest_file.close()
queue.put(file_name)
if __name__ == '__main__':
src_path = input('請(qǐng)輸入你要拷貝的目錄名:')
try:
dest_path = src_path + '-備份'
os.mkdir(dest_path)
file_list = os.listdir(src_path)
except Exception as e:
print(e)
else:
queue = multiprocessing.Queue()
for file in file_list:
pro = multiprocessing.Process(
target=copy_file, args=(file, src_path, dest_path, queue)
)
pro.start()
count = 0
while True:
queue.get()
count += 1
print('\r當(dāng)前進(jìn)度為%d%%' % (100.0 * count / len(file_list)),end='')
if count == len(file_list):
break