1. 程式人生 > 實用技巧 >Python並行系統工具_程式退出和程序間通訊

Python並行系統工具_程式退出和程序間通訊

與C語言不同,Python中沒有"主"函式,執行程式時,僅僅是自上而下,執行頂級檔案裡的所有程式碼

正常情況下,執行完最後一行語句時,自動退出

Sys模組退出

使用sysos模組中的工具顯示呼叫程式退出

sys.exit函式在呼叫時可以讓程式提前結束

  • 引數N:狀態

  • 其實只是丟擲了一個SystemExit異常

    • Exit the interpreter by raising SystemExit(status)

  • 可以捕獲異常,攔截程式的過早退出並執行清理活動

  • 如果未被捕獲,則直譯器正常退出

  • raise語句顯示丟擲SystemExit異常和呼叫sys.exit作用一樣

SystemExit異常

  • Exception都繼承自BaseException

  • 可以被捕獲

  • 如果捕獲它,直譯器在該處正常退出

def later():
    import sys
    print("Bye sys world")
    sys.exit(42)
    print("Never reached")


if __name__ == '__main__':
    later()
    
""" 程式執行結果
Bye sys world
Process finished with exit code 42
"""
sys.exit

OS模組退出

os.exit()

  • 呼叫程序立即退出,而不是丟擲可以捕獲或忽略的異常

  • 程序退出時進行輸出流緩衝和執行清理處理器(atexit模組定義)

  • 一般應當只在分支出的子程序上進行,最好不要在整個程式中進行

  • Exit to the system with specified status, without normal exit processing.

Shell 命令退出狀態程式碼

sysos退出呼叫接受程序退出狀態程式碼作為引數

  • sys可選

  • os 必須的

退出後,這個狀態程式碼可以在shell中查詢,也可以在以子程序執行該指令碼的程式中查詢

  • Shell命令列程式中,退出狀態可以在執行過程中以跨程式通訊的簡單形式得以檢查

  • 通過分支程序執行程式時,退出狀態可以在父程序中通過os.waitos.watipid呼叫獲取

os.systemos.popen執行shell名稱,在退出時獲取close(),之後需要從位掩碼中提取(>>8)

退出狀態實際上被包裝進返回值的特定位元位置需要將結果右移8位元才能讀取

❌但是在windows中,不需要進行右移,可以直接讀取

程序的退出狀態和共享狀態

"""
分支子程序,用os.wait觀察其退出狀態
能在Unix上執行,但是在Windows上不能執行
派生執行緒共享全域性變數,但分支程序擁有自己的全域性變數副本
分支程序複製並因此共享檔案描述符
"""

import os

exit_stat = 0


def child():
    global exit_stat
    exit_stat += 1 # 複製全域性變數,都是0,改變不影響其他分程序
    print('Hello from Child', os.getpid(), exit_stat)
    os._exit(exit_stat) # 傳送到父程序的wait函式的退出狀態
    print("Never reached")


def parent():
    while True:
        newpid = os.fork()
        if newpid == 0:
            child()
        else:
            pid, status = os.wait()
            print('Parent got', pid, status, (status >> 8))
            if input() == 'q':
                break


if __name__ == '__main__':
    parent()
Hello from Child 10271 1
Parent got 10271 256 1

Hello from Child 10272 1
Parent got 10272 256 1

Hello from Child 10273 1
Parent got 10273 256 1

Hello from Child 10274 1
Parent got 10274 256 1

Hello from Child 10275 1
Parent got 10275 256 1
退出狀態
  • 執行緒通常在其執行的函式返回時默默退出,且函式的返回值被忽略

  • 但是可以呼叫_thread.exit()結束其呼叫執行緒

  • _thread.exit()sys.exit相同,也會丟擲SystemExit異常

    • 所以也可以線上程中使用sys.exit

    • 但是一定不要使用os._exit(),會產生奇怪的結果

  • 執行緒一般不利用退出狀態,而是給模組水平的全域性變數複製或原位修改共享的可變物件以傳送訊號,並在必要時用執行緒的模組鎖和佇列來同步化共享物件的訪問

程序間通訊

程式派生執行緒時,有一個自然的通訊機制,即改變和檢查共享內容中的名稱和物件,包括可訪問的變數和屬性,以及被應用的可變物件,對於可能發生併發更新的共享物件需要小心的通過鎖來同步化對它們的訪問

  • 但是 執行緒還提供了一個相當直接的通訊模型queue模組

當程式開始一些子程序,以及一些不共享記憶體的獨立程式時,通訊就變得複雜了

  • 程式間通訊的機制(限制程式間可以通訊的型別)

    • 簡單的檔案

    • 命令列引數

    • 程式退出狀態碼

    • shell環境變數

    • 標準流重定向

    • os.popensubporcess 管理的管道流

  • Python庫提供的程序間通訊IPC工具

    • 訊號允許程式向其他程式傳送簡單的通知時間

    • 匿名管道允許共享檔案描述符的執行緒即相關程序傳遞資料,但是依賴於Unix下的程序分支模型

    • 命名管道對映到系統的檔案系統,允許完全不相關的程式進行交流,但是並非所有的Python平臺都提供

    • 套接字對映到系統級別的埠號,不僅允許在同一臺計算機上的任意兩個程式間傳遞資料,還允許遠端聯網的機器上的程式之間通訊

      雖然可以作為執行緒間通訊的手段,但在不共享記憶體空間的單獨程序的作用下,全部能力更能淋漓精緻的表現出來

管道

管道:程式間的通訊手段,是由作業系統實現並通過Python標準庫提供的

管道是單向的通道, 像一個共享的記憶體緩衝,但是對於兩端來說介面類似一個簡單的檔案

用法:一個程式在管道的一端寫入資料,而另一個程式在管道的另一端讀取資料,每個程式僅能看到自己一端的管道,並且使用正常的Python檔案呼叫方法來處理

管道更加偏向於在作業系統內部使用

  • 讀取管道的呼叫一般會阻塞呼叫程式,直到資料變得可用,而非返回一個檔案尾指示器

  • 管道的讀取呼叫總是返回最先寫入的資料(佇列,先進先出

  • 管道還可以用於同步化管獨立程式的執行

分類

  1. 具名管道

    • 通常稱為FIFO,計算機上由一個檔案代表

    • 具名管道是真正的外部檔案,進行通訊的程序無需相關,可以是獨立的啟動程式

  2. 匿名管道

    • 僅在程序內部存在, 通常和程序分支合用,作為一種在應用程式內部連線父程序與子程序的手段

    • 父程序與子程序通過共享的管道檔案描述符進行交流,後者為派生的程序所繼承

    • 匿名管道對執行緒也適用

匿名管道

建立管道
  • 返回的是兩端的描述符,分別代表著輸入端和輸出端

    • 但是沒有具體的分別

    • 只是一端寫入,另一端就只能讀取

  • 描述符的讀寫工具分別是os.reados.write,傳遞的是位元組字串

  • 讀取資料時,不會考慮資料本身是什麼,只會按照指定的數量讀取資料

    • 為了區分資訊,在管道中要求一個分割字元

      • 可以使用os.fdopen將檔案描述符封裝進一個檔案物件

      • 通過檔案物件的readline方法在管道中搜索寫一個/n分隔符

(read_fd, write_fd) = os.pipe()

## 匿名管道和程序分支
import os, time

def child(pipeout):
    zzz = 0
    while True:
        time.sleep(zzz)
        msg = ("Spam %03d" %zzz).encode()
        os.write(pipeout, msg)
        zzz = (zzz + 1) % 5 # 0,1,2,3,4,0 .。。。 迴圈

def parant():
    pipein, pipeout = os.pipe()
    if os.fork() == 0 :
        child(pipeout)
    else:
        while True:
            # 資料傳送完之前保持阻塞
            line = os.read(pipein, 32)
            print("Parent %d got [%s] at %s" % (os.getpid(), line, time.time()))

parant()
匿名管道和程序分支
import os
import time

start_time = time.time()


def child(pipeout):
    zzz = 0
    while True:
        time.sleep(zzz)
        msg = ("Spam %03d\n" % zzz).encode()
        os.write(pipeout, msg)
        zzz = (zzz + 1) % 5  # 0,1,2,3,4,0 .。。。 迴圈


def parant():
    pipein, pipeout = os.pipe()
    if os.fork() == 0:
        # 關閉輸入端
        os.close(pipein)
        child(pipeout)
    else:
        # 監聽管道,
        os.close(pipeout)  # 關閉輸出端
        # 建立文字模式的檔案物件
        pipein = os.fdopen(pipein)
        while True:
            # 資料傳送完之前保持阻塞
            # line = os.read(pipein, 32)
            line = pipein.readline()[:-1]
            print("Parent %d got [%s] at %s" % (os.getpid(), line, time.time()))


parant()
檔案文字模式
  • 在各程序中關閉管道未被使用的一端

  • 程式應當關閉未被使用的管道末端

## 匿名管道和執行緒
import os
import time
import threading
start_time = time.time()


def child(pipeout):
    zzz = 0
    while True:
        time.sleep(zzz)
        msg = ("Spam %03d" % zzz).encode()
        os.write(pipeout, msg)
        zzz = (zzz + 1) % 5  # 0,1,2,3,4,0 .。。。 迴圈


def parent(pipein):
    while True:
        # 資料傳送完成之前保持阻塞
        line = os.read(pipein, 32)
        print("Parent %d got [%s] at %s" % (os.getpid(), line, time.time() - start_time))


pi·
parent(pipein)
匿名管道和執行緒

      

## 匿名管道和執行緒import osimport timeimport threadingstart_time = time.time()

def child(pipeout): zzz = 0 while True: time.sleep(zzz) msg = ("Spam %03d" % zzz).encode() os.write(pipeout, msg) zzz = (zzz + 1) % 5 # 0,1,2,3,4,0 .。。。 迴圈

def parent(pipein): while True: # 資料傳送完成之前保持阻塞 line = os.read(pipein, 32) print("Parent %d got [%s] at %s" % (os.getpid(), line, time.time() - start_time))

pi·parent(pipein)