首頁>技術>

程序的概念:(Process)程序就是正在執行的程式,它是作業系統中資源分配的最小單位。資源分配:作業系統分配的CPU時間片、記憶體、磁碟空間埠等等資源。程序號(process identification)是作業系統分配給程序的唯一標識號,使用者每開啟一個程序作業系統都會為其建立PID。UID:使用者id; PID:程序id; PPID:父程序id。在儲存空間中未被執行的叫程式,被執行的叫程序(進行中的程式)。同一個程式執行兩次之後是兩個程序。程序和程序之間記憶體隔離,但可透過socket通訊或者透過檔案通訊。並行和併發併發:單個CPU透過作業系統排程以極快地速度輪流執行多個程序,給使用者的感覺是多個程序正在同時執行。併發在邏輯上是同時執行,實質上是輪流執行。並行:多個CPU在同一時間分別執行不同的程序。並行是真正的同時執行。CPU排程策略先來先服務:先來的先執行。短作業優先:短作業優先執行。時間片輪轉:每個作業執行一個時間片後輪轉其它作業執行。多級反饋佇列:程序首次啟動時進入優先順序最高的Q1佇列等待;優先執行Q1佇列中的程序;若高優先順序Q1佇列中無待執行的程序,那麼會執行Q2佇列中的程序;若Q1、Q2佇列中都無待執行的程序,那麼會執行Q3佇列中的程序;以此類推,直至末尾佇列的程序。對同級佇列中的程序按先來先服務的策略分配時間片。如Q1佇列的時間片為N,若Q1中的程序用完時間片N後還未完成時會被調整到Q2佇列;若用完Q2佇列的時間片後還未完成時會被調整到Q3佇列;以此類推,直至被調整到末尾佇列。在末尾佇列QN中的各個程序,按照時間片輪轉執行。如果低優先順序佇列中的程序在執行時有新程序需要執行,那麼它會被中斷執行並被放入當前佇列的隊尾,然後讓新程序優先執行。另外被中斷執行的程序再度執行時它只能得到上次未用完的時間片。優先順序越高的佇列時間片越短,優先順序越低的佇列時間片越長。如三級反饋佇列Q1、Q2、Q3它們的時間片分別為2、4、8。程序三狀態就緒(Ready)狀態:等待被CPU執行的狀態。執行(Running)狀態:正在被CPU正在執行的狀態。阻塞(Blocked)狀態:等待某個事件發生(如等待使用者輸入)而無法執行的狀態。

程式碼示意圖:

同步與非同步、阻塞與非阻塞

同步與非同步、阻塞與非阻塞發生在多工場景中:

同步:是指A程序呼叫B程序後,A程序要等B程序完成後才能繼續執行,這是單程序執行狀態。上面的程式碼示意圖中就是同步。非同步:是指A程序呼叫B程序後,A程序不等B程序完成,它和B程序可以同時執行,這是多程序(或執行緒)執行狀態。阻塞:是指程序呼叫了某些I/O操作後進入掛起狀態,要等I/O返回的結果才繼續執行。非阻塞:是指程序呼叫了某些I/O操作後不進入掛起狀態,不用等待I/O返回的結果就繼續執行。

四種狀態:

同步阻塞:單程序執行有阻塞事件時,等待阻塞事件完成後才能繼續執行。非同步阻塞:某程序執行時有多條子程序(或執行緒)同步執行,它的某條子程序有阻塞事件進入阻塞狀態,而其它子程序仍然正常執行。同步非阻塞:沒有阻塞事件,單程序正常執行的狀態。非同步非阻塞:多程序執行且無阻塞事件。多程序處理多程序的模組:注意:匯入的是Process類,首字母必須大寫,另外小寫的是process檔案。
from multiprocessing import Process
函數語言程式設計方式:

案例:

from multiprocessing import Processimport osdef func(n):    for i in range(n):        print("func", os.getpid(), os.getppid())if __name__ == '__main__':    print("main", os.getpid(), os.getppid())    p = Process(target=func, args=(1,))    p.start()    out:main 6928 6621func 6929 6928

程式碼說明:

os.getpid:獲取程序號。os.getppid:獲取父程序號。p = Process(target=func, args=(1,)):建立Process類的例項,目標是func函式,args=(1,)是給函式傳引數(注意agrs=後面必須是元祖,1後面的逗號不能少,少了就不是元祖,會報錯),以此建立子程序。面向物件式程式設計方式:

案例:

from multiprocessing import Processfrom time import sleepfrom os import getpid, getppidclass MyProcess(Process):    def __init__(self, n):        self.n = n        super().__init__()    def run(self):        sleep(0.1)        print(f"第{self.n}次列印,子程序id:{getpid()},父程序id:{getppid()}")if __name__ == "__main__":    for i in range(5):        MyProcess(i).start()    print("*" * 20)    out:********************第0次列印,子程序id:13029,父程序id:13028第2次列印,子程序id:13031,父程序id:13028第1次列印,子程序id:13030,父程序id:13028第3次列印,子程序id:13032,父程序id:13028第4次列印,子程序id:13033,父程序id:13028

程式碼說明:

面向物件程式設計必須自定義一個類並繼承Process類。必須使用__init__傳參,在傳參完成後必須呼叫父類的__init__方法才能正確完成初始化。建立子程序的方法就是初始化自定義類的方法。Process類常用的屬性和方法:
class Process(process.BaseProcess):    _start_method = None    @staticmethod    def _Popen(process_obj):        return _default_context.get_context().Process._Popen(process_obj)

Process類本身內容很少,但它繼承了process.BaseProcess類,再來看process.BaseProcess類:

class BaseProcess:    name: str    daemon: bool    authkey: bytes    def __init__(        self,        group: None = ...,        target: Optional[Callable[..., Any]] = ...,        name: Optional[str] = ...,        args: Tuple[Any, ...] = ...,        kwargs: Mapping[str, Any] = ...,        *,        daemon: Optional[bool] = ...,    ) -> None: ...    def run(self) -> None: ...    def start(self) -> None: ...    def terminate(self) -> None: ...    if sys.version_info >= (3, 7):        def kill(self) -> None: ...        def close(self) -> None: ...    def join(self, timeout: Optional[float] = ...) -> None: ...    def is_alive(self) -> bool: ...    @property    def exitcode(self) -> Optional[int]: ...    @property    def ident(self) -> Optional[int]: ...    @property    def pid(self) -> Optional[int]: ...    @property    def sentinel(self) -> int: ...

程式碼說明:

run方法:即多程序需要執行的程式碼主體。start方法:即多程序啟動執行。terminate方法:強制終止子程序。請注意這個方法是呼叫作業系統來關閉子程序,通常需要零點零零幾秒的片刻時間後該子程序才會被終止。這是非同步非阻塞的方法,即該方法通知作業系統後會立即繼續執行後續程式碼,它不等待作業系統返回結果,後續程式碼執行的時候作業系統殺程序的程式碼也在同步執行的。is_alive方法:檢視子程序是否活著。用self.terminate方法結束子程序後立即檢視可能還是True即活著的狀態,要等待作業系統執行完殺程序的操作後才會返回False。pid和ident屬性:它們是被property裝飾的方法,返回當前程序的id,這2個屬性內容完全一致。self.exitcode屬性:返回子程序結束時的狀態碼。不同作業系統平臺下的差異:windows平臺下建立子程序是透過載入py檔案來獲取所需的資料和程式碼,假如不寫“ if __name__ == ‘__main__’ ”會造成遞迴載入py檔案導致載入失敗!linux和mac平臺下建立子程序是透過複製父程序記憶體空間來獲取的所需的資料和程式碼,所以在linux和mac平臺下不寫“ if __name__ == ‘__main__’ ”也可以正常執行,不會導致載入失敗!在linux平臺下建立和執行子程序的效率比window平臺下高得多。不同子程序之間記憶體隔離,不能直接共享資料。但是可以透過socket通訊。開啟多個子程序的示範:
from multiprocessing import Processimport timedef func(name):    time.sleep(0.5)    print('子程序:', name)if __name__ == '__main__':    print("父程序:")    name_list = ['張三', '李四', '王五']    for i in name_list:        p = Process(target=func, args=(i,))        p.start()out:父程序:子程序: 張三子程序: 王五子程序: 李四

程式碼說明:

在該案例中time.sleep(0.5)是阻塞事件,多條子程序各自執行,遇到阻塞時各自等待,相互不干擾。這就是非同步阻塞。可以使用迴圈的方式建立多條子程序。join阻塞主程序,主程序等待被join的子程序執行結束後才繼續執行:

模擬多程序下載檔案的錯誤程式碼:

from multiprocessing import Processimport timeimport randomdef func(name):    time.sleep(random.random())    print('子程序:', name)if __name__ == '__main__':    name_list = ['下載完第一部分', '下載完第二部分', '下載完第三部分', '下載完第四部分', '下載完第五部分']    for i in name_list:        p = Process(target=func, args=(i,))        p.start()    print("檔案五個部分下載完成,合併完畢!")out:檔案五個部分下載完成,合併完畢!子程序: 下載完第四部分子程序: 下載完第一部分子程序: 下載完第五部分子程序: 下載完第二部分子程序: 下載完第三部分

程式碼說明:

上述程式碼模擬多程序下載檔案。假設不阻塞主程序,那麼這個程式無法保證正確執行。要保證上述程式碼正常執行就必須阻塞主程序,等待所有子程序下載完畢後主程序才能繼續執行後續的合併和校驗檔案以及告知使用者下載完成的工作。

模擬多程序下載檔案的正確程式碼:

from multiprocessing import Processimport timeimport randomdef func(name):    time.sleep(random.random())    print('子程序:', name)if __name__ == '__main__':    name_list = ['下載完第一部分', '下載完第二部分', '下載完第三部分', '下載完第四部分', '下載完第五部分']    process_list = []    for i in name_list:        p = Process(target=func, args=(i,))        p.start()        process_list.append(p)    for i in process_list:        i.join()    print("檔案五個部分下載完成,合併完畢!")    out:子程序: 下載完第二部分子程序: 下載完第五部分子程序: 下載完第一部分子程序: 下載完第三部分子程序: 下載完第四部分檔案五個部分下載完成,合併完畢!

程式碼說明:

在上述程式碼中,每次建立並開啟子程序後,會將子程序的物件記憶體地址存入process_list列表中。所有子程序建立完畢後,遍歷process_list列表,將所有子程序物件設為阻塞事件,設定方法是p.join()。守護程序:給子程序設定守護程序屬性為True,該子程序會隨著主程序程式碼執行完畢而結束。
p.daemon = True
設定守護程序屬性語句必須在子程序啟動語句前面。
p.daemon = Truep.start()
守護程序內無法再開啟子程序。

案例,看案例請思考一個問題,son1的列印語句會執行幾次?

import timefrom multiprocessing import Processdef son1():    while True:        print('->1號子程序')        time.sleep(1)def son2():    for i in range(5):        print('->2號子程序')        time.sleep(1)if __name__=="__main__":    p1 = Process(target=son1)    p1.daemon = True    p1.start()    p2 = Process(target=son2)    p2.start()    print("->主程序")    time.sleep(3)    out:->主程序->1號子程序->2號子程序->1號子程序->2號子程序->1號子程序->2號子程序->2號子程序->2號子程序

程式碼說明:

a. 在上述案例中,son1函式即1號子程序每隔1秒列印"->1號子程序"(無限迴圈);

b. son2函式即2號子程序每隔1秒列印"->1號子程序"(迴圈5次);

c. 在主程序程式碼中對son1設守護程序屬性為True,然後啟動son1子程序;

d. 對son2未設守護程序屬性(預設為False),然後啟動son2子程序;

e. 主程序sleep3秒,顯示結果是son1列印了3次。即主程序的程式碼3秒執行完畢後son1子程序會被強制結束!

f. son2子程序未設daemon屬性,它正常列印了5次,它不會隨著主程序的程式碼結束而結束。

結論:

a. 子程序daemon屬性為True的是守護程序,在主程序程式碼結束時它會被強制結束;

b. 子程序daemon屬性為False的是非守護程序,在主程序程式碼結束時它仍然會正常執行直至執行完畢。

c. 主程序程式碼結束後守護程序會立即結束,之後python直譯器還會做一些回收資源的工作,最後主程序才真正結束。

21
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 負載均衡 | 一致性hash演算法