Python實現(xiàn)多進程(一)

一. 多任務(wù)編程

多任務(wù)編程指利用計算機的多核特點,同時執(zhí)行多個任務(wù)。通過充分地利用計算機資源吼句,來提高程序的運行效率。如多線程和多進程編程事格。在開始學習多任務(wù)編程前惕艳,區(qū)分如下幾個概念非常重要

1.1 并行和并發(fā)

當多個計算機核心同時處理多個任務(wù)時,多個任務(wù)之間是并行關(guān)系驹愚,因此并行數(shù)量取決于計算機核心數(shù)远搪。計算機同時處理多個任務(wù),內(nèi)核在多個任務(wù)之間不斷切換逢捺,達到好像在同時處理的運行效果谁鳍,此時多個任務(wù)實際為并發(fā)關(guān)系,如IO多路復(fù)用劫瞳。

舉一個生活中的列子:當只有一個咖啡機時倘潜,兩隊人排隊交替使用一個咖啡機,這是并發(fā)志于;而當我們有2臺咖啡機時涮因,兩隊人員就能同時使用各自的咖啡機,互補干擾伺绽。那么回到計算機中:

  • 并行(parallel):指在同一時刻养泡,有多條指令在多個處理器上同時執(zhí)行嗜湃。所以無論從微觀還是從宏觀來看,二者都是一起執(zhí)行的澜掩。
  • 并發(fā)(concurrency):指在同一時刻只能有一條指令執(zhí)行净蚤,但多個進程指令被快速的輪換執(zhí)行,使得在宏觀上具有多個進程同時執(zhí)行的效果输硝,但在微觀上并不是同時執(zhí)行的今瀑,只是把時間分成若干段,使多個進程快速交替的執(zhí)行点把。

1.2 進程和程序

進程:程序在計算機中運行一次的過程橘荠,是一個動態(tài)的過程描述,占有CPU內(nèi)存等計算機資源郎逃,具有一定的生命周期哥童。同一個程序的不同執(zhí)行過程,分配的計算機資源不同褒翰,屬于不同的進程贮懈。

一個進程的創(chuàng)建流程如下:

  • 用戶空間運行一個程序,發(fā)起進程創(chuàng)建
  • 操作系統(tǒng)接受用戶請求优训,開啟進程創(chuàng)建
  • 操作系統(tǒng)分配系統(tǒng)資源朵你,確認進程狀態(tài)
  • 將創(chuàng)建好的進程提供給應(yīng)用層使用

程序:靜態(tài)的可執(zhí)行文件,占有磁盤揣非,不占有計算機的運行資源抡医;磁盤不屬于計算機資源。

二. 進程

2.1 相關(guān)概念

CPU時間片:如果一個進程占有計算機核心早敬,我們稱為該進程在CPU的時間片上忌傻。多個任務(wù)實際對CPU會進行爭奪,一般由操作系統(tǒng)分配CPU時間片搞监。

進程控制塊PCB(Processing Control Block):在操作系統(tǒng)中水孩,進程創(chuàng)建后會自動產(chǎn)生一個空間存放進程信息,稱為進程控制塊琐驴。進程信息包含進程PID俘种、進程占有的內(nèi)存位置、創(chuàng)建時間棍矛、用戶安疗、狀態(tài)等。

進程PID:進程在操作系統(tǒng)中的唯一編號够委,是一個大于0的整數(shù)荐类,由系統(tǒng)自動分配。

進程的特征:進程是操作系統(tǒng)分配“計算機資源”的最小單位茁帽。每個進程都有自己單獨的虛擬內(nèi)存空間玉罐,進程間的執(zhí)行相互獨立屈嗤,互不影響。

ps -aux 查看進程信息

STAT 描述
D 等待態(tài)吊输,且為不可中斷等待
S 等待態(tài)饶号,為可中斷等待
T 等待態(tài),為暫停狀態(tài)
R 就緒態(tài)季蚂,運行態(tài)
Z 僵尸態(tài) 茫船,進程已經(jīng)結(jié)束,但仍存在一些問題解決
+ 前臺進程
< 高優(yōu)先級
N 低優(yōu)先級
I 有進程鏈接
s 會話組組長

進程的狀態(tài)

三態(tài):

  • 就緒:進程具備執(zhí)行條件扭屁,等待系統(tǒng)分配CPU
  • 執(zhí)行:進程占有CPU時間片算谈,處于運行狀態(tài)
  • 等待:進程暫時不具備運行條件,需要阻塞等待

五態(tài)(增加新建態(tài)和終止態(tài)):

  • 新建:創(chuàng)建一個新的進程料滥,獲取資源的過程
  • 終止:進程結(jié)束然眼,釋放資源的過程
  • 進程優(yōu)先級:優(yōu)先級決定了一個進程的執(zhí)行權(quán)限和占有資源的優(yōu)先程度
  • top命令 : 動態(tài)地查看進程優(yōu)先級,其中NI的取值范圍為-20~19葵腹,其中-20優(yōu)先級最高
  • nice : 運行程序時指定優(yōu)先級高每;
sudo nice -9 ./while.py  # 指定以-9優(yōu)先級來運行程序

父子進程:除了初始化進程,每個進程都有一個父進程践宴,也可能有0個或者多個子進程鲸匿。

  • pstree:查看進程樹
  • ps -ajx:查看父進程PID

進程的相關(guān)函數(shù)

  • os.getpid():獲取當前進程的PID
  • os.getppid():獲取父進程的PID
  • os._exit(status):退出進程,使用status整數(shù)標識進程的退出狀態(tài)浴井,并通過人為賦予相應(yīng)的含義
  • sys.exit([status]):默認為0晒骇,如果是整數(shù),則表示退出狀態(tài)磺浙;如果是字符串,則表示在退出時打印內(nèi)容

孤兒進程:父進程先于子進程退出徒坡,此時子進程就會成為孤兒進程撕氧。孤兒進程會被系統(tǒng)指定的進程收養(yǎng),即系統(tǒng)進程會成為該孤兒進程新的父進程喇完,處理孤兒進程的退出狀態(tài)伦泥。因此孤兒進程一定不會成為僵尸進程。

僵尸進程:子進程先于父進程退出锦溪,并且父進程沒有處理子進程退出狀態(tài)不脯,此時子進程成為僵尸進程。僵尸進程已經(jīng)結(jié)束刻诊,但是會滯留部分PCB信息在內(nèi)存防楷,大量的僵尸會消耗系統(tǒng)資源,應(yīng)該盡量避免僵尸進程的產(chǎn)生则涯。

三. 進程的創(chuàng)建

3.1 使用os.fork函數(shù)

  • os.fork()創(chuàng)建子進程失敗時返回負數(shù)复局,成功時返回一個非負數(shù)冲簿;
  • 在子進程中,返回值為0亿昏;在父進程中峦剔,返回值為子進程的PID;
  • 子進程從pid = os.fork()語句之后開始執(zhí)行角钩,但子進程會復(fù)制父進程的全部代碼段吝沫。
import os
import time
 
a = 1
print(time.ctime(), '程序開始執(zhí)行:')
 
pid = os.fork()  # 創(chuàng)建子進程
if pid < 0:
    print('子進程創(chuàng)建失敗!')
elif pid == 0:
    # 子進程
    time.sleep(2)
    print('子進程:a=', a)
    a = 1000
    print('在子進程中將a的值設(shè)為1000')
else:
    print('子進程PID:', pid)
    time.sleep(4)
    print('父進程:a=',a)  # 空間獨立,互不影響
 
print(time.ctime(), '程序執(zhí)行完畢!')

運行結(jié)果:

3.2 如何避免僵尸進程的產(chǎn)生

當子進程先于父進程退出递礼,如果父進程沒有處理子進程退出狀態(tài)野舶,此時子進程成為僵尸進程。因為為了避免產(chǎn)生僵尸宰衙,首先想到的即讓“父進程先于子進程退出”平道,具體實現(xiàn)如下:

  • 父進程創(chuàng)建子進程等待子進程退出;
  • 子進程創(chuàng)建二級子進程供炼,然后立馬退出一屋;
  • 二級子進程成為孤兒,處理具體事宜袋哼;
import os
from time import sleep, ctime
 
def func1():
    sleep(3)
    print(ctime(), 'fun1()')
 
def func2():
    sleep(4)
    print(ctime(), 'fun2()')
 
pid = os.fork()
 
if pid < 0:
    print('create process failed!')
elif pid == 0:
    # 創(chuàng)建二級子進程
    pid0 = os.fork()
    if pid0 < 0:
        print('create 2th process failed!')
    elif pid0 == 0:
        func2()
        print(ctime(), '二級子進程退出!')
    else:
        # 一級子進程在創(chuàng)建二級子進程后立刻退出冀墨,不會造成父進程的阻塞
        print(ctime(), '一級子進程退出')
        os._exit(0)
else:
    print(ctime(), '父進程阻塞等待回收一級子進程')
    os.wait()
    func1()
    print(ctime(), '父進程退出')

運行結(jié)果:

避免僵尸的第二種方法即在父進程中阻塞等待處理子進程的退出,使用我們上例中用到的os.wait即可涛贯。os.wait()返回值為子進程PID以及子進程的退出狀態(tài)(默認為0)诽嘉。

import os
import sys
from time import sleep, ctime
 
pid = os.fork()
 
if pid < 0:
    print("Create process failed!")
elif pid == 0:
    sleep(3)
    print(ctime(), "子進程{}即將退出".format(os.getpid()))
    sys.exit(2)
else:
    print(ctime(), '父進程阻塞等待子進程{}的退出'.format(pid))
    # 阻塞等待子進程退出
    pid,status = os.wait()
    print(ctime(), '父進程已處理子進程{}的退出'.format(pid))
    # 退出狀態(tài)2 * 256,便于系統(tǒng)的識別
    print(pid,status)
    # 打印sys.exit(2)設(shè)置的退出狀態(tài)
    print(os.WEXITSTATUS(status))

運行結(jié)果:

和os.wait方法類似,os模塊還提供了一個os.waitpid(pid,option)方法弟翘,pid可以指定處理的子進程PID虫腋,option則可以指定阻塞和非阻塞。具體如下:

  • pid,status = os.waitpid(pid,option) 處理子進程的退出
  • pid -1:任意子進程退出稀余;>0:指定PID號的子進程退出悦冀;
  • option 0:阻塞等待;WNOHANG:非阻塞
  • pid, status = waitpid(-1,0) 等價于pid, status = wait()
import os
import sys
from time import sleep
 
pid = os.fork()
 
if pid < 0:
    print("Create process failed!")
elif pid == 0:
    sleep(3)
    print("子進程{}即將退出".format(os.getpid()))
    sys.exit(2)
else:
    # 等待子進程退出
    while True:
        sleep(1)
        #-1表示任意子進程 os.WNOHANG表示非阻塞
        pid,status = os.waitpid(-1,os.WNOHANG) 
        #status即子進程的退出狀態(tài)sys.exit(2) * 256
        print(pid,status)
        if os.WEXITSTATUS(status):
            print("主進程已處理{}進程的退出".format(pid))
            break
        print("Do something others!")

運行結(jié)果:

除了使用上述的os.wait / os.waitpid在主進程中處理子進程的退出狀態(tài)外睛琳,更常用的一種方式是利用系統(tǒng)內(nèi)核盒蟆,異步地處理子進程退出問題。實現(xiàn)過程大致如下:

  • 在多任務(wù)程序初始化時师骗,(注意历等,需要導(dǎo)入signal標準庫模塊)聲明signal.signal(signal.SIGCHLD, signal.SIG_IGN);
  • 內(nèi)核發(fā)現(xiàn)子進程退出時辟癌,會給主進程發(fā)送一個SIGCHLD信號寒屯,signal的第二個參數(shù)是主進程對子進程的處理方式;
  • SIG_IGN即主進程忽略子進程的退出愿待,并交由系統(tǒng)處理回收子進程的退出浩螺。(此處不再詳細舉例靴患,后續(xù)介紹進程間通信方式時,再對信號做詳細介紹)

3.2 使用multiprocessing模塊

multiprocessing模塊中要出,需要將要做的事情封裝為函數(shù)鸳君,使用multiprocessing中提供的Process類,在實例化時傳入target函數(shù)患蹂,創(chuàng)建進程對象或颊。p.start啟動進程后,會自動執(zhí)行相關(guān)聯(lián)函數(shù)传于,并在事件完成后回收進程囱挑。此過程用到的主要類、方法如下:

類/方法 參數(shù)
Process(name,target,*args,**kwargs) 創(chuàng)建進程對象: name:給創(chuàng)建的進程對象起一個名字沼溜,default:Process-n平挑;target:綁定的事件函數(shù);args:給“target”函數(shù)'按照位置傳參系草;kwargs:給“target”函數(shù)'按照關(guān)鍵字傳參通熄;
P.start() 啟動進程,并自動執(zhí)行進程函數(shù)找都。
P.join([timeout]) 阻塞等待回收響應(yīng)的進程唇辨。timeout可選參數(shù):超時時間
'''通常使用multiprocessing創(chuàng)建進程,父進程只用作進程的創(chuàng)建和回收能耻,不做其他工作赏枚。'''
import os, time
import multiprocessing as mp
 
a = 1
 
def func():
    '''修改子進程空間的內(nèi)容,不會對父進程產(chǎn)生影響'''
    global a
    a = 100
    print('在子進程中a =',a)
    print('子進程{}已完成func的執(zhí)行晓猛,即將返回主進程{}'.format(os.getpid(), os.getppid()))
 
p = mp.Process(target=func)  # 創(chuàng)建進程對象
p.start()  # 啟動進程
print('在父進程{}中創(chuàng)建了子進程{}'.format(os.getpid(), p.pid))
time.sleep(5)  # 延遲5s,使子進程修改了變量a綁定的值
print('父進程中:a = ',a)
 
p.join()  # 回收子進程,有效防止了僵尸進程的產(chǎn)生

運行結(jié)果:

multiprocessing創(chuàng)建進程總結(jié)如下:

multiprocessing創(chuàng)建進程是原來進程的子進程饿幅,創(chuàng)建后父子進程各自執(zhí)行互不影響。子進程同樣是復(fù)制父進程的空間鞍帝,子進程對內(nèi)容的修改诫睬,不會影響父進程空間。join回收子進程帕涌,會有效阻止僵尸進程產(chǎn)生。

進程對象的屬性:

  • p.name:進程名稱续徽,默認為Process-1蚓曼,可自定義名稱;
  • p.pid:創(chuàng)建進程的PID
  • p.daemon:默認值為False钦扭,父進程退出纫版,不會影響子進程的運行;設(shè)置為True時客情,父進程退出其弊,子進程也將退出癞己;daemon 的設(shè)置必須在start前。如果設(shè)置daemon為True梭伐,則不再使用join痹雅,在子進程退出前,會自動做處理糊识;
  • p.is_alive() 判斷進程生命周期狀態(tài)绩社,返回True/False

通常使用multiprocessing創(chuàng)建進程,父進程只用作進程的創(chuàng)建和回收赂苗,不做其他工作愉耙。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拌滋,隨后出現(xiàn)的幾起案子朴沿,更是在濱河造成了極大的恐慌,老刑警劉巖败砂,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赌渣,死亡現(xiàn)場離奇詭異,居然都是意外死亡吠卷,警方通過查閱死者的電腦和手機锡垄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來祭隔,“玉大人货岭,你說我怎么就攤上這事〖部剩” “怎么了千贯?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長搞坝。 經(jīng)常有香客問我搔谴,道長,這世上最難降的妖魔是什么桩撮? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任敦第,我火速辦了婚禮,結(jié)果婚禮上店量,老公的妹妹穿的比我還像新娘芜果。我一直安慰自己,他們只是感情好融师,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布右钾。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舀射。 梳的紋絲不亂的頭發(fā)上窘茁,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天,我揣著相機與錄音脆烟,去河邊找鬼山林。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播券腔,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼亿笤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎左驾,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體极谊,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡诡右,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了轻猖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片帆吻。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖咙边,靈堂內(nèi)的尸體忽然破棺而出猜煮,到底是詐尸還是另有隱情,我是刑警寧澤败许,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布王带,位于F島的核電站,受9級特大地震影響市殷,放射性物質(zhì)發(fā)生泄漏愕撰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一醋寝、第九天 我趴在偏房一處隱蔽的房頂上張望搞挣。 院中可真熱鬧,春花似錦音羞、人聲如沸柿究。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春办陷,著一層夾襖步出監(jiān)牢的瞬間貌夕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工民镜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留啡专,地道東北人。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓制圈,卻偏偏與公主長得像们童,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鲸鹦,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

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

  • 進程 程序運行中會有一個默認的進程慧库,如果想要創(chuàng)建子進程,需要用miltiprocessing實現(xiàn)馋嗜。 具體哪個進程先...
    奧爾良雞腿腿閱讀 310評論 0 1
  • 學編程最有效的方法是動手敲代碼葛菇。 目錄 1甘磨,什么是多進程 我們所寫的Python 代碼就是一個程序,Python ...
    碼農(nóng)充電站pro閱讀 318評論 0 2
  • Python多進程使用與總結(jié) 1.概要 ? 眾所周知眯停,由于GIL鎖的存在济舆,Python多線程并不是真正意義上的...
    千鳥月讀閱讀 1,930評論 0 1
  • 本文轉(zhuǎn)載自實驗樓:多進程(一) 概述 進程的概念這里就不再過多的贅述了,市面上幾乎關(guān)于計算機操作系統(tǒng)的書都有詳細的...
    mnikn閱讀 494評論 0 0
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月莺债,有人笑有人哭滋觉,有人歡樂有人憂愁,有人驚喜有人失落九府,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,535評論 28 53