python進程

要讓Python程序?qū)崿F(xiàn)多進程(multiprocessing),我們先了解操作系統(tǒng)的相關(guān)知識。Unix/Linux操作系統(tǒng)提供了一個fork()系統(tǒng)調(diào)用,它非常特殊。普通的函數(shù)調(diào)用,調(diào)用一次,返回一次,但是fork()調(diào)用一次,返回兩次,因為操作系統(tǒng)自動把當前進程(稱為父進程)復制了一份(稱為子進程),然后,分別在父進程和子進程內(nèi)返回。
子進程永遠返回0,而父進程返回子進程的ID。這樣做的理由是,一個父進程可以fork出很多子進程,所以,父進程要記下每個子進程的ID,而子進程只需要調(diào)用getppid()就可以拿到父進程的ID。Python的os模塊封裝了常見的系統(tǒng)調(diào)用,其中就包括fork。可以在linux系統(tǒng)下的Python程序中輕松創(chuàng)建子進程:

import os
import time

pid = os.fork()
if pid == 0:
        print('哈哈。。。')
else:
        print('嘿嘿。。。')

print('中午了。。。')

結(jié)果如下:



可以利用多進程調(diào)用函數(shù):

import os
import time

def sing():
        for i in range(3):
                print('唱歌。。。')
                print(time.ctime())
                time.sleep(1)

def dance():
        for i in range(3):
                print('跳舞。。。')
                print(time.ctime())
                time.sleep(1)

if __name__ == '__main__':
        pid = os.fork()
        
        if pid == 0:
                sing()
        else:
                dance()

結(jié)果如下:



在前面提到了進程號,什么是進程號(PID)呢?打開我們的資源管理器:



可以使用os模塊里的getpid()得到自己的進程號,使用getppid()得到父進程的進程號。
import os
pid = os.fork()
print(pid)
if pid == 0:
        print('哈哈。。。')
        print('我是子進程:%s,我的父進程:%s'%(os.getpid(),os.getppid()))
else:
        print('嘿嘿。。。')
        print('我是父進程:%s,我的父進程:%s'%(os.getpid(),os.getppid()))

結(jié)果如下:



可以清楚的看到,父進程返回的值是子進程的進程號。
當有一個全局變量時,在父進程或子進程中改變?nèi)肿兞浚瑫ζ渌M程有影響嗎?

import os
import time

num = 10
pid = os.fork()
print(pid)
if pid == 0:
        num = num +10
        print('哈哈。。。num=%s'%num)
        print('我是子進程:%s,我的父進程:%s'%(os.getpid(),os.getppid()))
else:
        time.sleep(3)
        num = num*10
        print('嘿嘿。。。num=%s'%num)
        print('我是父進程:%s,我的父進程:%s'%(os.getpid(),os.getppid()))

結(jié)果如下:



正如之前所說,當執(zhí)行fork()語句時,子進程會將父進程的代碼拷貝一份,各執(zhí)行各的,互不影響。
那么多個fork是如何執(zhí)行的?


import os
import time

pid = os.fork()
if pid == 0:
        print('哈哈1。。。')
else:
        print('主進程嘿嘿1。。。')

pid = os.fork()
if pid == 0:
        print('哈哈2。。。')
else:
        print('主進程嘿嘿2。。。')

結(jié)果如下:



如果你打算編寫多進程的服務程序,Unix/Linux無疑是正確的選擇。由于Windows沒有fork調(diào)用,難道在Windows上無法用Python編寫多進程的程序?由于Python是跨平臺的,自然也應該提供一個跨平臺的多進程支持。multiprocessing模塊就是跨平臺版本的多進程模塊。

import multiprocessing
import os


def foo():
    print('子進程id:%s父進程id:%s' % (os.getpid(), os.getppid()))


if __name__ == '__main__':
    print('父進程id:%s' % os.getpid())
    p = multiprocessing.Process(target=foo)
    print('子進程將要執(zhí)行。。。')
    p.start()
    print('主進程結(jié)束。。')

結(jié)果如下:



對比linux下fork()的多進程,出現(xiàn)了明顯的區(qū)別,在第一個例子中,父進程和子進程都打印了‘中午了’。而在multiprocessing的模塊下,子進程只進行自己函數(shù)里的內(nèi)容,并沒有打印‘主程序結(jié)束這句話’。只有主程序打印了這句話。
Process語法結(jié)構(gòu)如下:



target:表示這個進程實例所調(diào)用對象;
args:表示調(diào)用對象的位置參數(shù)元組;

kwargs:表示調(diào)用對象的關(guān)鍵字參數(shù)字典;
name:為當前進程實例的別名;
group:大多數(shù)情況下用不到;
Process類常用方法;
is_alive():判斷進程實例是否還在執(zhí)行;
join([timeout]):是否等待進程實例執(zhí)行結(jié)束,或等待多少秒;
start():啟動進程實例(創(chuàng)建子進程);
run():如果沒有給定target參數(shù),對這個對象調(diào)用start()方法時,就將執(zhí)行對象中的run()方法;
terminate():不管任務是否完成,立即終止;
Process類常用屬性;
name:當前進程實例別名,默認為Process-N,N為從1開始遞增的整數(shù);
pid:當前進程實例的PID值;
創(chuàng)建子進程時,只需要傳入一個執(zhí)行函數(shù)和函數(shù)的參數(shù),創(chuàng)建一個Process實例,用start()方法啟動,這樣創(chuàng)建進程比fork()還要簡單。join()方法可以等待子進程結(jié)束后再繼續(xù)往下運行,通常用于進程間的同步。代碼如下:

import multiprocessing
import os


def foo():
    print('子進程id:%s父進程id:%s' % (os.getpid(), os.getppid()))


if __name__ == '__main__':
    print('父進程id:%s' % os.getpid())
    p = multiprocessing.Process(target=foo)
    print('子進程將要執(zhí)行。。。')
    p.start()
    p.join()
    print('主進程結(jié)束。。')

結(jié)果如下:



傳遞其他的參數(shù)代碼如下:

import multiprocessing
import os


def foo(name, no, **kwargs):
    print('%s子進程%s的id:%s父進程id:%s' % (no, name, os.getpid(), os.getppid()))
    print(kwargs)


if __name__ == '__main__':
    print('父進程id:%s' % os.getpid())
    p1 = multiprocessing.Process(target=foo, name='哈哈', args=('xx', 18), kwargs={'1': 'haha', '2': 'hehe'})
    p2 = multiprocessing.Process(target=foo, args=('ff', 30), kwargs={'xx': '哈哈', 'kk': '嘿嘿'})
    print('子進程將要執(zhí)行。。。')
    p1.start()
    print(p1.name)
    p2.start()
    print(p2.name)
    p1.join()
    p2.join()
    print('主進程結(jié)束。。')

結(jié)果如下:



可以看出如果設置name,打印進程的名字是按設置的name來,如果不設置,系統(tǒng)默認的是Process-x。
有了以上的基礎,就可以試著模擬多進程的下載過程。

import multiprocessing
import time
import random


def download(name):
    print('文件%s添加至下載隊列。。。' % name)
    time.sleep(random.randint(2, 5))
    print('文件%s下載成功。。。' % name)


if __name__ == '__main__':
    p1 = multiprocessing.Process(target=download, name='哈哈', args=('三國_01.avi',))
    p2 = multiprocessing.Process(target=download, args=('三國_02.avi',))
    p3 = multiprocessing.Process(target=download, args=('三國_03.avi',))
    print('將要開始下載。。。')
    p1.start()
    p2.start()
    p3.start()
    p1.join()
    p2.join()
    p3.join()
    print('下載結(jié)束。。')

結(jié)果如下:



創(chuàng)建進程Process的子類。也就是說創(chuàng)建新的進程還能夠使用類的方法,可以自定義一個類,繼承Process類,每次實例化這個類的時候,就等同于實例化一個進程對象。

import multiprocessing
import os


class MyProcess(multiprocessing.Process):
    def __init__(self, name):
        super().__init__(name=name)

    def run(self):
        print('我是子進程:%s,我的PID是%s' % (self.name, os.getpid()))
        print('我的父進程PID是:%s' % os.getppid())


if __name__ == '__main__':
    print('程序開始。。。')
    print('程序PID是:%s' % os.getpid())
    p1 = MyProcess('進程一')
    p1.start()
    p1.join()
    print('程序結(jié)束。。。')

輸出結(jié)果:


因為Process類本身也有init方法,這個子類相當于重寫了這個方法。所以再次調(diào)用父類的方法,完成初始化操作。
當需要創(chuàng)建的子進程數(shù)量不多時,可以直接利用multiprocessing中的Process動態(tài)成生多個進程,但如果是上百甚至上千個目標,手動的去創(chuàng)建進程的工作量巨大,此時就可以用到multiprocessing模塊提供的Pool方法。初始化Pool時,可以指定一個最大進程數(shù),當有新的請求提交到Pool中時,如果池還沒有滿,那么就會創(chuàng)建一個新的進程用來執(zhí)行該請求;但如果池中的進程數(shù)已經(jīng)達到指定的最大值,那么該請求就會等待,直到池中有進程結(jié)束,才會創(chuàng)建新的進程來執(zhí)行。

import multiprocessing
import os, time, random


def worker(msg):
    t_start = time.time()
    print("%s開始執(zhí)行,進程號為%d" % (msg, os.getpid()))
    # random.random()隨機生成0~1之間的浮點數(shù)
    time.sleep(random.random() * 2)
    t_stop = time.time()
    print(msg, "執(zhí)行完畢,耗時%0.2f" % (t_stop - t_start))


if __name__ == '__main__':

    po = multiprocessing.Pool(3)  # 定義一個進程池,最大進程數(shù)3
    for i in range(0, 10):
        # Pool.apply_async(要調(diào)用的目標,(傳遞給目標的參數(shù)元祖,))
        # 每次循環(huán)將會用空閑出來的子進程去調(diào)用目標
        po.apply_async(worker, (i,))

    print("----start----")
    po.close()  # 關(guān)閉進程池,關(guān)閉后po不再接收新的請求
    po.join()  # 等待po中所有子進程執(zhí)行完成,必須放在close語句之后
    print("-----end-----")

結(jié)果如下:



multiprocessing.Pool常用函數(shù)解析:
apply_async(func[, args[, kwds]]) :使用非阻塞方式調(diào)用func(并行執(zhí)行,堵塞方式必須等待上一個進程退出才能執(zhí)行下一個進程),args為傳遞給func的參數(shù)列表,kwds為傳遞給func的關(guān)鍵字參數(shù)列表。
close():關(guān)閉Pool,使其不再接受新的任務;
terminate():不管任務是否完成,立即終止;
join():主進程阻塞,等待子進程的退出, 必須在close或terminate之后使用;
apply(func[, args[, kwds]]):使用阻塞方式調(diào)用func。

import multiprocessing
import os, time, random


def worker(msg):
    t_start = time.time()
    print("%s開始執(zhí)行,進程號為%d" % (msg, os.getpid()))
    time.sleep(random.random() * 2)
    t_stop = time.time()
    print(msg, "執(zhí)行完畢,耗時%0.2f" % (t_stop - t_start))


if __name__ == '__main__':
    #print("----start----")
    po = multiprocessing.Pool(3)
    for i in range(0, 10):
        po.apply(worker, (i,))
    print("----start----")
    po.close()
    po.join()
    print("-----end-----")

結(jié)果如下:



process之間有時需要通信,操作系統(tǒng)提供了很多機制來實現(xiàn)進程間的通信。以下使用Queue進行通信。

import multiprocessing

q = multiprocessing.Queue(3)
q.put('哈哈')
q.put('嘿嘿')
print(q.full())
q.put('呵呵')
print(q.full())
try:
    q.put('第四次', timeout=3)
except:
    print('消息隊列已滿,當前消息數(shù)量:%s' % q.qsize())
if not q.empty():
    for i in range(q.qsize()):
        print(q.get())

結(jié)果如下:



初始化Queue()對象時(例如:q=Queue()),若括號中沒有指定最大可接收的消息數(shù)量,或數(shù)量為負值,那么就代表可接受的消息數(shù)量沒有上限(直到內(nèi)存的盡頭);
Queue.qsize():返回當前隊列包含的消息數(shù)量;
Queue.empty():如果隊列為空,返回True,反之False ;
Queue.full():如果隊列滿了,返回True,反之False;
Queue.get([block[, timeout]]):獲取隊列中的一條消息,然后將其從列隊中移除,block默認值為True;
1)如果block使用默認值,且沒有設置timeout(單位秒),消息列隊如果為空,此時程序?qū)⒈蛔枞ㄍT谧x取狀態(tài)),直到從消息列隊讀到消息為止,如果設置了timeout,則會等待timeout秒,若還沒讀取到任何消息,則拋出"Queue.Empty"異常;
2)如果block值為False,消息列隊如果為空,則會立刻拋出"Queue.Empty"異常;
Queue.get_nowait():相當Queue.get(False);
Queue.put(item,[block[, timeout]]):將item消息寫入隊列,block默認值為True;
1)如果block使用默認值,且沒有設置timeout(單位秒),消息列隊如果已經(jīng)沒有空間可寫入,此時程序?qū)⒈蛔枞ㄍT趯懭霠顟B(tài)),直到從消息列隊騰出空間為止,如果設置了timeout,則會等待timeout秒,若還沒空間,則拋出"Queue.Full"異常;
2)如果block值為False,消息列隊如果沒有空間可寫入,則會立刻拋出"Queue.Full"異常;
Queue.put_nowait(item):相當Queue.put(item, False);
下面在主進程中創(chuàng)建兩個子進程,一個往Queue里寫入數(shù)據(jù),一個從Queue里讀數(shù)據(jù)。

import multiprocessing
import time
import random


# 寫數(shù)據(jù)進程執(zhí)行的代碼:
def write(q):
    for value in ['A', 'B', 'C']:
        print('Put %s to queue...' % value)
        q.put(value)
        time.sleep(random.random())


# 讀數(shù)據(jù)進程執(zhí)行的代碼:
def read(q):
    while True:
        if not q.empty():
            value = q.get(True)
            print('Get %s from queue.' % value)
            time.sleep(random.random())
        else:
            break


if __name__ == '__main__':
    # 主(父)進程創(chuàng)建Queue,并傳給各個子進程:
    q = multiprocessing.Queue()
    pw = multiprocessing.Process(target=write, args=(q,))
    pr = multiprocessing.Process(target=read, args=(q,))
    # 啟動子進程pw,寫入:
    pw.start()
    # 等待pw結(jié)束:
    pw.join()
    # 啟動子進程pr,讀取:
    pr.start()
    pr.join()
    print('所有數(shù)據(jù)都寫入并且讀完')

結(jié)果如下:



如果要使用Pool創(chuàng)建進程,就需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue(),否則會得到一條如下的錯誤信息:
RuntimeError: Queue objects should only be shared between processes through inheritance.
下面的實例演示了進程池中的進程如何通信

import multiprocessing
import os


def reader(q):
    print('reader啟動(%s),父進程為(%s)' % (os.getpid(), os.getppid()))
    for i in range(q.qsize()):
        print('reader從Queue獲取到消息:%s' % q.get(True))


def writer(q):
    print('writer啟動(%s),父進程為(%s)' % (os.getpid(), os.getppid()))
    for i in 'ABCD':
        q.put(i)


if __name__ == '__main__':
    print('(%s) start' % os.getpid())
    q = multiprocessing.Manager().Queue()  # 使用Manager中的Queue來初始化
    po = multiprocessing.Pool()
    # 使用阻塞模式創(chuàng)建進程,這樣就不需要在reader中使用死循環(huán)了,可以讓writer完全執(zhí)行完成后,再用reader去讀取
    po.apply_async(writer, (q,))
    po.apply_async(reader, (q,))
    po.close()
    po.join()
    print('(%s) End' % os.getpid())

結(jié)果如下:



如果使用非阻塞式
po.apply_async(writer, (q,))po.apply_async(reader, (q,))
可能出現(xiàn)如下結(jié)果:



因為cpu的分配不一定誰先誰后,所以可能發(fā)生先進行讀的進程,才開始寫的進程。因此出現(xiàn)了上圖的結(jié)果。
小結(jié)

在Unix/Linux下,可以使用fork()調(diào)用實現(xiàn)多進程。
要實現(xiàn)跨平臺的多進程,可以使用multiprocessing模塊。
進程間通信是通過Queue實現(xiàn)的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,237評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,957評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,248評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,356評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,081評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,485評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,534評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,720評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,263評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,025評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,204評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,787評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,461評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,874評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,105評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,945評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,205評論 2 375

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

  • 一多任務的引入 有很多的場景中的事情是同時進行的,比如開車的時候手和腳共同來駕駛汽車,再比如唱歌跳舞也是同時進行的...
    五行缺覺閱讀 663評論 0 0
  • 轉(zhuǎn)自:http://www.liaoxuefeng.com/wiki/0014316089557264a6b348...
    放風箏的小小馬閱讀 225評論 0 2
  • 1.進程 1.1多線程的引入 現(xiàn)實生活中 有很多的場景中的事情是同時進行的,比如開車的時候手和腳共同來駕駛汽車,再...
    TENG書閱讀 510評論 0 0
  • 一、進程的概念 相信很多同學都聽說過windows、linux,MacOS都是多任務,多用戶的操作系統(tǒng)。那什么是多...
    轉(zhuǎn)身后的那一回眸閱讀 998評論 0 1
  • 1.1.1多任務的引入 什么叫“多任務”呢?簡單地說,就是操作系統(tǒng)可以同時運行多個任務。打個比方,你一邊在用瀏覽器...
    PythonMaO閱讀 474評論 0 1