Python:多進(jìn)程和多線程

在現(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í)行了兩次,為什么了又憨,看下面的圖翠霍。


1.png

主進(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é)果如下


2.png

我們發(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ì)使用)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末投队,一起剝皮案震驚了整個(gè)濱河市祖能,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蛾洛,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雁芙,死亡現(xiàn)場(chǎng)離奇詭異轧膘,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)兔甘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)谎碍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人洞焙,你說(shuō)我怎么就攤上這事蟆淀。” “怎么了澡匪?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵熔任,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我唁情,道長(zhǎng)疑苔,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任甸鸟,我火速辦了婚禮惦费,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抢韭。我一直安慰自己薪贫,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布刻恭。 她就那樣靜靜地躺著瞧省,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鳍贾。 梳的紋絲不亂的頭發(fā)上臀突,一...
    開(kāi)封第一講書(shū)人閱讀 52,268評(píng)論 1 309
  • 那天,我揣著相機(jī)與錄音贾漏,去河邊找鬼候学。 笑死,一個(gè)胖子當(dāng)著我的面吹牛纵散,可吹牛的內(nèi)容都是我干的梳码。 我是一名探鬼主播隐圾,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼掰茶!你這毒婦竟也來(lái)了暇藏?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤濒蒋,失蹤者是張志新(化名)和其女友劉穎盐碱,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體沪伙,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓮顽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了围橡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暖混。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖翁授,靈堂內(nèi)的尸體忽然破棺而出拣播,到底是詐尸還是另有隱情,我是刑警寧澤收擦,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布贮配,位于F島的核電站,受9級(jí)特大地震影響塞赂,放射性物質(zhì)發(fā)生泄漏牧嫉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一减途、第九天 我趴在偏房一處隱蔽的房頂上張望酣藻。 院中可真熱鬧,春花似錦鳍置、人聲如沸辽剧。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)怕轿。三九已至,卻和暖如春辟拷,著一層夾襖步出監(jiān)牢的瞬間撞羽,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工衫冻, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诀紊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓隅俘,卻偏偏與公主長(zhǎng)得像邻奠,于是被迫代替她去往敵國(guó)和親笤喳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容