在現(xiàn)實(shí)社會(huì)那伐,我們經(jīng)常需要一種場(chǎng)景,就是同時(shí)有多個(gè)事情需要執(zhí)行石蔗,如在瀏覽網(wǎng)頁(yè)的同時(shí)需要聽(tīng)音樂(lè)罕邀。比如說(shuō)在跳舞的時(shí)候要唱歌。
同樣的抓督,在程序中我們也可能需要這種場(chǎng)景燃少。如下面我們以同時(shí)聽(tīng)音樂(lè)和瀏覽網(wǎng)頁(yè)為例。
def network():
while True:
print("正在上網(wǎng)~~~")
time.sleep(1)def sing():
while True:
print("正在聽(tīng)歌……")
time.sleep(1)
if __name__ == "__main__":
network()
sing()
執(zhí)行代碼铃在,我們發(fā)現(xiàn)代碼并沒(méi)有按照我們的想法執(zhí)行阵具,而是,一直執(zhí)行上網(wǎng)的代碼定铜,這樣就無(wú)法完成我們想要的效果阳液。
如果你想要同時(shí)聽(tīng)音樂(lè)和瀏覽網(wǎng)頁(yè)兩件事,就需要一個(gè)新的方式來(lái)完成揣炕,這個(gè)方式就是----多任務(wù)帘皿。
多任務(wù)是咋回事呢?
什么叫做多任務(wù)呢畸陡?簡(jiǎn)單的說(shuō)鹰溜,就是操作系統(tǒng)(OS)可以同時(shí)運(yùn)行多個(gè)任務(wù)。如你可以一邊瀏覽網(wǎng)頁(yè)丁恭,一邊聽(tīng)著歌曹动,同時(shí)還可以使用畫(huà)板畫(huà)著畫(huà),這個(gè)就是多任務(wù)牲览。其實(shí)我們的操作系統(tǒng)就是多任務(wù)墓陈,只是我們都沒(méi)有注意罷了。
單核CPU中:
1、時(shí)間片輪換
2贡必、優(yōu)先級(jí)別調(diào)度
3兔港、多核CPU中:
4、并發(fā)
5仔拟、并行
Fork子進(jìn)程
編寫(xiě)完的代碼衫樊,在沒(méi)有運(yùn)行的情況下,稱之為程序理逊。
正在運(yùn)行的代碼橡伞,就成為了進(jìn)程。
進(jìn)程晋被,除了包含代碼外兑徘,還需要運(yùn)行環(huán)境等,所以和程序是存在區(qū)別的羡洛。
Python在os模塊上封裝了常用的系統(tǒng)調(diào)用挂脑,其中就包括了fork,我們可以很輕松地在Python代碼中創(chuàng)建進(jìn)程欲侮。
# 注意崭闲,下面代碼只能在Linux、Mac系統(tǒng)上運(yùn)行威蕉,window下不支持運(yùn)行5蠹蟆!韧涨!
import osimport time# 使用os模塊的fork方法可以創(chuàng)建一個(gè)新的進(jìn)程
pid = os.fork()if pid == 0:
while True:
print("son process", pid) # 子進(jìn)程代碼
time.sleep(1)
else:
while True:
print("main process", pid) # 父進(jìn)程代碼
time.sleep(1)
Python中的fork() 函數(shù)可以獲得系統(tǒng)中進(jìn)程的PID ( Process ID )牍戚,返回0則為子進(jìn)程,否則就是父進(jìn)程虑粥,然后可以據(jù)此對(duì)運(yùn)行中的進(jìn)程進(jìn)行操作如孝;
但是強(qiáng)大的 fork() 函數(shù)在Windows版的Python中是無(wú)法使用的。娩贷。第晰。只能在Linux系統(tǒng)中使用,比如 Ubuntu 15.04彬祖,Windows中獲取父進(jìn)程ID可以用 getpid()茁瘦。
getpid、getppid
我們可以使用os模塊的getpid來(lái)獲取當(dāng)前進(jìn)程的進(jìn)程編號(hào)储笑,同樣也可以使用os.getppid()方法案來(lái)獲取當(dāng)前進(jìn)程的父進(jìn)程編號(hào)甜熔。
import osimport time# 使用os模塊的fork方法可以創(chuàng)建一個(gè)新的進(jìn)程
pid = os.fork()
if pid == 0:
while True:
print("我是子線程,我的編號(hào)是%s南蓬,我的父進(jìn)程編號(hào)是%s(os.getpid(),os.getppid()))
time.sleep(1)
else:
while True:
print("我是父線程纺非,我的編號(hào)是%s轩缤,我的子進(jìn)程編號(hào)是%s" %(os.getpid(), pid))
time.sleep(1)
使用os模塊中的getpid()方法羡儿,可以得到當(dāng)前線程的編號(hào)呀狼,使用getppid()方法可以得到該線程的父級(jí)編號(hào)弯洗。
多進(jìn)程修改全局變量多進(jìn)程修改全局變量
import os
import time
# 使用os模塊的fork方法可以創(chuàng)建一個(gè)新的進(jìn)程
pid = os.fork()
if pid == 0:
while True:
print("我是子線程廉白,我的編號(hào)是%s垂睬,我的父進(jìn)程編號(hào)是%s" %(os.getpid(),os.getppid()))
time.sleep(1)
else:
while True:
print("我是父線程鉴竭,我的編號(hào)是%s干毅,我的子進(jìn)程編號(hào)是%s" %(os.getpid(), pid))
time.sleep(1)
使用os模塊中的getpid()方法跳夭,可以得到當(dāng)前線程的編號(hào)涂圆,使用getppid()方法可以得到該線程的父級(jí)編號(hào)。
多進(jìn)程修改全局變量
import os
# 定義一個(gè)全局變量
num = 0
pid = os.fork()
if pid == 0:
# 子進(jìn)程
num += 1
print("子進(jìn)程~~~~~~",num)
else:
#父進(jìn)程
num += 1
print("父進(jìn)程~~~~~~", num)
輸出結(jié)果如下
父進(jìn)程~~~~~~ 1
子進(jìn)程~~~~~~ 1
由此可見(jiàn)币叹,進(jìn)程間是無(wú)法共享數(shù)據(jù)的润歉。
注意:多個(gè)進(jìn)程間,每個(gè)進(jìn)程的所有數(shù)據(jù)(包括全局變量)都是各自擁有一份的颈抚,互不影響踩衩。
完成最開(kāi)始的任務(wù)
import os
import time
def network():
for x in range(5):
print("正在上網(wǎng)~~~")
time.sleep(1)
def singe():
for x in range(5):
print("正在聽(tīng)歌……")
time.sleep(1)
pid = os.fork()
if pid == 0:
network()
else:
singe()
print("程序結(jié)束~~~")
多個(gè)fork問(wèn)題
上面的所有程序中,我們使用了fork函數(shù)贩汉,生成了兩個(gè)進(jìn)程(一個(gè)主進(jìn)程驱富、一個(gè)子進(jìn)程),但是如果我們?cè)诔绦蛑行枰鄠€(gè)進(jìn)程呢匹舞?如兩次調(diào)用fork函數(shù)褐鸥,會(huì)生成三個(gè)進(jìn)程嗎?
import os,time
res = os.fork()
if res == 0:
print("一個(gè)子線程")
else:
print("主線程")
ret = os.fork()
if ret == 0:
print("第三個(gè)線程")
else:
print("第四個(gè)線程")
結(jié)果如下
主線程
第四個(gè)線程
第三個(gè)線程
一個(gè)子線程
第三個(gè)線程
第四個(gè)線程
我們發(fā)現(xiàn)赐稽,主線程和子線程各執(zhí)行了一次叫榕,但是第三個(gè)和第四個(gè)都執(zhí)行了兩次,為什么了又憨,看下面的圖翠霍。
主進(jìn)程和子進(jìn)程的執(zhí)行順序
其實(shí)通過(guò)上面的代碼,我們已經(jīng)發(fā)現(xiàn)了蠢莺,主進(jìn)程和子進(jìn)程的執(zhí)行順序是沒(méi)有規(guī)律的寒匙,這個(gè)不受程序員的控制,有操作系統(tǒng)的調(diào)度算法來(lái)控制躏将。
multiprocessing
前面我們使用os.fork()方法實(shí)現(xiàn)了多進(jìn)程锄弱,但是這種方案只能在Linux下運(yùn)行,window環(huán)境下是無(wú)法運(yùn)行的祸憋,那么有沒(méi)有可以實(shí)現(xiàn)在任何平臺(tái)都能運(yùn)行的多進(jìn)程了会宪,有!Python為大家提供了multiprocessing模塊用來(lái)實(shí)現(xiàn)多進(jìn)程蚯窥。
函數(shù)實(shí)現(xiàn)方式
from multiprocessing import Process
import os
def run():
print("這個(gè)是一個(gè)獨(dú)立的進(jìn)程",os.getpid(),os.getppid())
if __name__ == "__main__":
print("代碼開(kāi)始運(yùn)行……")
task = Process(target=run)
task.start() # 啟動(dòng)進(jìn)程
print("代碼運(yùn)行結(jié)束……",os.getpid())
結(jié)果如下
我們發(fā)現(xiàn)主進(jìn)程結(jié)束后掸鹅,子進(jìn)程才結(jié)束塞帐,說(shuō)明我們確實(shí)開(kāi)辟了一個(gè)獨(dú)立的進(jìn)程。
from multiprocessing import Process
import os
def run(msg):
for x in range(5):
print("這個(gè)是一個(gè)獨(dú)立的進(jìn)程",os.getpid(),os.getppid())
print("傳遞的參數(shù)是:",msg)
else:
print("子進(jìn)程結(jié)束了……")
if __name__ == "__main__":
print("代碼開(kāi)始運(yùn)行……")
# target表示獨(dú)立進(jìn)程的方法
#args表示要傳遞的參數(shù)巍沙,注意:args的類型是元組,也支持列表list
task = Process(target=run,args=("這個(gè)是參數(shù)",))
task.start() # 啟動(dòng)進(jìn)程
print("代碼運(yùn)行結(jié)束……",os.getpid())
args為調(diào)用的子進(jìn)程的函數(shù)的參數(shù)葵姥,注意類型是一個(gè)元組。
from multiprocessing import Process
import os
def run(msg):
for x in range(5):
print("這個(gè)是一個(gè)獨(dú)立的進(jìn)程",os.getpid(),os.getppid())
print("傳遞的參數(shù)是:",msg)
else:
print("子進(jìn)程結(jié)束了……")
if __name__ == "__main__":
print("代碼開(kāi)始運(yùn)行……")
# target表示獨(dú)立進(jìn)程的方法
#args表示要傳遞的參數(shù)句携,注意:args的類型是元組
task1 = Process(target=run,args=("這個(gè)是參數(shù)1",))
task1.start() # 啟動(dòng)進(jìn)程
task2 = Process(target=run, args=("這個(gè)是參數(shù)2",))
task2.start() # 啟動(dòng)進(jìn)程
task3 = Process(target=run, args=("這個(gè)是參數(shù)3",))
task3.start() # 啟動(dòng)進(jìn)程
print("代碼運(yùn)行結(jié)束……",os.getpid())
多個(gè)進(jìn)程啟動(dòng)還是一樣榔幸,執(zhí)行的順序同樣不可控。
(主進(jìn)程等待子進(jìn)程版):
join方法表示只有子進(jìn)程執(zhí)行完成后矮嫉,主進(jìn)程才能結(jié)束削咆。主進(jìn)程會(huì)等待子進(jìn)程完成后,自身才會(huì)執(zhí)行完成蠢笋。
from multiprocessing import Process
import os,time
def run(msg):
print("子進(jìn)程開(kāi)始執(zhí)行了……")
time.sleep(3)
print("子進(jìn)程執(zhí)行end了……")
if __name__ == "__main__":
print("代碼開(kāi)始運(yùn)行……")
task1 = Process(target=run,args=("這個(gè)是參數(shù)1",))
print(task1.is_alive()) # is_alive()方法判斷進(jìn)程是否結(jié)束
task1.join() # 表示這個(gè)子進(jìn)程執(zhí)行完成拨齐,主進(jìn)程才能繼續(xù)向下執(zhí)行
print(task1.is_alive())
print("代碼運(yùn)行結(jié)束……",os.getpid())
常用方法
from multiprocessing import Process
import os,time
def run(msg):
print("子進(jìn)程開(kāi)始執(zhí)行了……")
time.sleep(3)
print("子進(jìn)程執(zhí)行end了……")
if __name__ == "__main__":
print("代碼開(kāi)始運(yùn)行……")
# name表示我們認(rèn)為的為這個(gè)子進(jìn)程取個(gè)名稱,
# 如果不寫(xiě)昨寞,默認(rèn)是Process-n n從1開(kāi)始
task1 = Process(target=run,args=("這個(gè)是參數(shù)1",),name="liujianhong")
task1.start() # 啟動(dòng)進(jìn)程
print(task1.is_alive()) # is_alive()方法判斷進(jìn)程是否結(jié)束
task1.join() # 表示這個(gè)子進(jìn)程執(zhí)行完成奏黑,主進(jìn)程才能繼續(xù)向下執(zhí)行
print(task1.name) # 得到子進(jìn)程的名稱
task1.terminate() # 強(qiáng)制結(jié)束進(jìn)程
print(task1.is_alive())
print("代碼運(yùn)行結(jié)束……",os.getpid())
多個(gè)進(jìn)程使用不同的方法版
from multiprocessing import Process
import os,time
def run(msg):
print("子進(jìn)程1開(kāi)始執(zhí)行了……")
time.sleep(3)
print("子進(jìn)程1執(zhí)行end了……")
def run2(msg):
print("子進(jìn)程2開(kāi)始執(zhí)行了……")
time.sleep(3)
print("子進(jìn)程2執(zhí)行end了……")
if __name__ == "__main__":
print("代碼開(kāi)始運(yùn)行……")
task1 = Process(target=run,args=("這個(gè)是參數(shù)1",))
task1.start() # 啟動(dòng)進(jìn)程
task2 = Process(target=run2, args=("這個(gè)是參數(shù)2",))
task2.start() # 啟動(dòng)進(jìn)程
print("代碼運(yùn)行結(jié)束……",os.getpid())
類實(shí)現(xiàn)方式
在Python中,很多的方案都提供了函數(shù)和類兩種實(shí)現(xiàn)方式编矾,如:裝飾器熟史、自定義元類。同樣多進(jìn)程也有兩種實(shí)現(xiàn)窄俏,前面我們已經(jīng)看了使用函數(shù)實(shí)現(xiàn)的方式蹂匹,下面我們使用類來(lái)實(shí)現(xiàn)以下唄。
進(jìn)程類的實(shí)現(xiàn)非常的簡(jiǎn)單凹蜈,只要繼承了Process類就ok了限寞,重新該類的run方法,run方法里面的代碼仰坦,就是我們需要的子進(jìn)程代碼履植。
from multiprocessing import Process
import time
class MyProcess(Process):
# 重寫(xiě)run方法即可
def run(self):
print("一個(gè)子進(jìn)程開(kāi)始運(yùn)行了")
time.sleep(1)
print("一個(gè)子進(jìn)程開(kāi)始運(yùn)行結(jié)束了")
if __name__ == '__main__':
task = MyProcess()
task.start()
print("主進(jìn)程結(jié)束了……")
在進(jìn)程類的實(shí)現(xiàn)中如果想要初始化一些前面我們提到過(guò)的參數(shù),如進(jìn)程名稱等悄晃,可以使用init借助父類來(lái)完成玫霎。
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self,name):
super().__init__(name=name)
# 重寫(xiě)run方法即可
def run(self):
print("一個(gè)子進(jìn)程開(kāi)始運(yùn)行了")
time.sleep(1)
print("一個(gè)子進(jìn)程開(kāi)始運(yùn)行結(jié)束了")
if __name__ == '__main__':
task = MyProcess("劉建宏")
task.start()
print(task.name)
print("主進(jìn)程結(jié)束了……")
進(jìn)程池Pool
當(dāng)我們需要的進(jìn)程數(shù)量不多的時(shí)候,我們可以使用multiprocessing的Process類來(lái)創(chuàng)建進(jìn)程妈橄。但是如果我們需要的進(jìn)程特別多的時(shí)候庶近,手動(dòng)創(chuàng)建工作量太大了,所以Python也為我們提供了Pool(池)的方式來(lái)創(chuàng)建大量進(jìn)程眷蚓。
from multiprocessing import Pool
import os,time
def run(msg):
print("開(kāi)始一個(gè)子線程運(yùn)行了……")
time.sleep(1)
print("開(kāi)始一個(gè)子線程運(yùn)行結(jié)束了……")
if __name__ == "__main__":
pool = Pool(3) # 表示初始化一個(gè)進(jìn)程池鼻种,最大進(jìn)程數(shù)為5
for x in range(10):
pool.apply_async(run, args=("hello pool",))
print("------start----")
pool.close() # 關(guān)閉池
pool.join() # 等待所有的子進(jìn)程完成,必須放在close后面
print("-------end------")
注意:一般我們使用apply_async這個(gè)方法沙热,表示非阻塞的運(yùn)行叉钥,一旦使用了apply方法表示阻塞式執(zhí)行任務(wù)罢缸,此時(shí)就是單任務(wù)執(zhí)行了(一般不會(huì)使用,特殊場(chǎng)景才會(huì)使用)