首頁 > 軟體

Python的程序及程序池詳解

2021-11-25 19:01:45

程序

程序是作業系統分配資源的基本單元,是程式隔離的邊界。

程序和程式

程式只是一組指令的集合,它本身沒有任何執行的含義,它是靜態的。

程序程式的執行範例,是動態的,有自己的生命週期,有建立有復原,存在是暫時的。

程序和程式不是一一對應的,一個程式可以對應多個程序,一個程序也可以執行一個或者多個程式。

我們可以這樣理解:編寫完的程式碼,沒有執行時稱為程式,正在執行的程式碼,會啟動一個(或多個)程序。

程序的狀態

在我們的作業系統⼯作時,任務數往往⼤於cpu核心數,即⼀定有⼀些任務正在執⾏,⽽另外⼀些任務在等待cpu,因此導致了程序有不同的狀態。

  • 就緒狀態:已滿⾜運⾏條件,等待cpu執⾏
  • 執⾏狀態:cpu正在執⾏
  • 等待狀態:等待某些條件滿⾜,比如⼀個程式sleep了,此時就處於等待狀態

Python中的程序

在Python中,程序是通過multiprocessing多程序模組來建立的,multiprocessing模組提供了⼀個Process類來建立程序物件。

建立⼦程序

Process語法結構:

Process(group, target, name, args, kwargs)

  • group:指定行程群組,⼤多數情況下⽤不到
  • target:表示呼叫物件,即子程序要執行的任務
  • name:子程序的名稱,可以不設定
  • args:給target指定的函數傳遞的引數,以元組的⽅式傳遞
  • kwargs:給target指定的函數傳遞命名引數

Process常用方法

  • p.start() 啟動程序,並呼叫該子程序中的p.run()方法
  • p.join(timeout):主程序等待⼦程序執⾏結束再結束,timeout是可選的超時時間
  • is_alive():判斷程序⼦程序是否還存活
  • p.run() 程序啟動時執行的方法,正是它去呼叫target指定的函數
  • p.terminate() ⽴即終⽌⼦程序

Process建立的範例物件的常⽤屬性

name:當前程序的別名,預設為Process-N,N為從1開始遞增的整數

pid:當前程序的pid(程序號)

import multiprocessing
import os
import time
def work(name):
    print("子程序work正在執行......")
    time.sleep(0.5)
    print(name)
    # 獲取程序的名稱
    print("子程序name", multiprocessing.current_process())
    # 獲取程序的pid
    print("子程序pid", multiprocessing.current_process().pid, os.getpid())
    # 獲取父程序的pid
    print("父程序pid", os.getppid())
    print("子程序執行結束......")
if __name__ == '__main__':
    print("主程序啟動")
    # 獲取程序的名稱
    print("主程序name", multiprocessing.current_process())
    # 獲取程序的pid
    print("主程序pid", multiprocessing.current_process().pid, os.getpid())
    # 建立程序
    p = multiprocessing.Process(group=None, target=work, args=("tigeriaf", ))
    # 啟動程序
    p.start()
    print("主程序結束")

通過上述程式碼我們發現,multiprocessing.Process幫我們建立一個子程序,並且成功執行,但是我們發現,在子程序還沒執行完的時候主程序就已經死了,那麼這個子程序在主程序結束後就是一個孤兒程序,那麼我們可以讓主程序等待子程序結束後再結束嗎?答案是可以的。 那就是通過p.join(),join()的作用是讓主程序等子程序執行完再退出。

import multiprocessing
import os
import time
def work(name):
    print("子程序work正在執行......")
    time.sleep(0.5)
    print(name)
    # 獲取程序的名稱
    print("子程序name", multiprocessing.current_process())
    # 獲取程序的pid
    print("子程序pid", multiprocessing.current_process().pid, os.getpid())
    # 獲取父程序的pid
    print("父程序pid", os.getppid())
    print("子程序執行結束......")
if __name__ == '__main__':
    print("主程序啟動")
    # 獲取程序的名稱
    print("主程序name", multiprocessing.current_process())
    # 獲取程序的pid
    print("主程序pid", multiprocessing.current_process().pid, os.getpid())
    # 建立程序
    p = multiprocessing.Process(group=None, target=work, args=("tigeriaf", ))
    # 啟動程序
    p.start()
    p.join()
    print("主程序結束")

執行結果:

可以看出,主程序是在子程序結束後才結束的。

全域性變數問題

全域性變數在多個程序中不共用,程序之間的資料是獨立的,預設情況下互不影響。

import multiprocessing
# 定義全域性變數
num = 99
def work1():
    print("work1正在執行......")
    global num   # 在函數內部宣告使⽤全域性變數num
    num = num + 1  # 對num值進⾏+1
    print("work1 num = {}".format(num))
def work2():
    print("work2正在執行......")
    print("work2 num = {}".format(num))
if __name__ == '__main__':
    # 建立程序p1
    p1 = multiprocessing.Process(group=None, target=work1)
    # 啟動程序p1
    p1.start()
    # 建立程序p2
    p2 = multiprocessing.Process(group=None, target=work2)
    # 啟動程序p2
    p2.start()

執行結果:

從運⾏結果可以看出,work1()函數對全域性變數num的修改,在work2中並沒有獲取到,⽽還是原來的99,所以,程序之間是不夠共用變數的。

守護行程

上面說到,可以使用p.join()讓主程序等待子程序結束後再結束,那麼可不可以讓子程序在主程序結束的時候就結束呢?答案是肯定的。 我們可以使用p.daemon = True或者p2.terminate()進行設定:

import multiprocessing
import time
def work1():
    print("work1正在執行......")
    time.sleep(4)
    print("work1執行完畢")
def work2():
    print("work2正在執行......")
    time.sleep(10)
    print("work2執行完畢")
if __name__ == '__main__':
    # 建立程序p1
    p1 = multiprocessing.Process(group=None, target=work1)
    # 啟動程序p1
    p1.start()
    # 建立程序p2
    p2 = multiprocessing.Process(group=None, target=work2)
    # 設定p2守護主程序
    # 第⼀種⽅式
    # p2.daemon = True  在start()之前設定,不然會拋異常
    # 啟動程序p2
    p2.start()
    time.sleep(2)
    print("主程序執行完畢!")
    # 第⼆種⽅式 
    p2.terminate()

執行結果如下:

由於p2設定了守護主程序,所以主程序執行完畢後,p2子程序也隨之結束,work2任務停止,而work1繼續執行至結束。

程序池

當需要建立的⼦程序數量不多時, 可以直接利⽤multiprocessing.Process動態生成多個程序, 但如果要建立很多程序時,⼿動建立的話⼯作量會非常大,此時就可以⽤到multiprocessing模組提供的Pool去建立一個程序池。

multiprocessing.Pool常⽤函數:

  • apply_async(func, args, kwds):使⽤⾮阻塞⽅式調⽤func(任務並⾏執⾏),args為傳遞給func的參數列,kwds為傳遞給func的關鍵字參數列
  • apply(func, args, kwds):使⽤阻塞⽅式調⽤func,必須等待上⼀個程序執行完任務後才能執⾏下⼀個程序,瞭解即可,幾乎不用
  • close():關閉Pool,使其不再接受新的任務
  • terminate():不管任務是否完成,⽴即終⽌
  • join():主程序阻塞,等待⼦程序的退出,必須在close或terminate之後使⽤

初始化Pool時,可以指定⼀個最⼤程序數,當有新的任務提交到Pool中時,如果程序池還沒有滿,那麼就會建立⼀個新的程序⽤來執⾏該任務,但如果程序池已滿(池中的程序數已經達到指定的最⼤值),那麼該任務就會等待,直到池中有程序結束才會建立新的程序來執⾏。

from multiprocessing import Pool
import time
def work(i):
    print("work'{}'執行中......".format(i), multiprocessing.current_process().name, multiprocessing.current_process().pid)
    time.sleep(2)
    print("work'{}'執行完畢......".format(i))
if __name__ == '__main__':
    # 建立程序池
    # Pool(3) 表示建立容量為3個程序的程序池
    pool = Pool(3)
    for i in range(10):
        # 利⽤程序池同步執⾏work任務,程序池中的程序會等待上⼀個程序執行完任務後才能執⾏下⼀個程序
        # pool.apply(work, (i, ))
        # 使⽤非同步⽅式執⾏work任務
        pool.apply_async(work, (i, ))
    # 程序池關閉之後不再接受新的請求
    pool.close()
    # 等待po中所有子程序結束,必須放在close()後面, 如果使⽤非同步⽅式執⾏work任務,主執行緒不再等待⼦執行緒執⾏完畢再退出!
    pool.join()

執行結果為:

從結果我們可以看出,只有3個子程序在執行任務,此處我們使用的是非同步⽅式(pool.apply_async(work, (i, )))執⾏work任務,如果是以同步方式(pool.apply(work, (i, )))執行,程序池中的程序會等待上⼀個程序執行完任務後才能執⾏下⼀個程序。

總結

本篇只介紹了什麼是程序、程序與程式的關係、程序的建立與使用、建立程序池等,並沒有介紹程序同步及程序通訊等,下篇文章將會介紹。


IT145.com E-mail:sddin#qq.com