# [Python] Process(程序)/Thread(執行緒)/GIL(全局解釋器鎖/Deadlock(死鎖)) ###### tags: `後端` ### 程序(Process) vs 執行緒(Thread) vs協程(Coroutine) ![](https://i.imgur.com/0C7t7ON.png) https://longzonwu.com/software-development/operation-system/what-is-process-thread-coroutine/ 1. `Program`(程式): >開發者開發出來的**程式碼**,**尚未載入到memory** 2. `Process`(程序, **進程**): >**啟動應用程式(program)時產生的執行實體,將program載入到memory,需要一定的CPU與記憶體等資源**,才有辦法完成工作。彼此互相獨立,**不會共用記憶體資源**。比如說,像是開啟word和excel。 > >**Process 是 Thread 的容器,一個 Process 中可以持有多個 Thread** 3. `Thread`(執行緒, **線程**): >就是**執行工作的基本單位**。**同一個程序(Process)中的執行緒(Thread)之間,可以共用記憶體資源**。比如說,word裏頭繪圖跟編輯文字兩個功能就是在同一個程序(Process)裡但不同的執行緒(Thread)。 底下是python的threading模組的基本應用: ```python= import threading import time def sleep(s): print("Let's sleep\t ", time.ctime()) time.sleep(s) print("Wake up\t ", time.ctime()) t = threading.Thread(target=sleep, args=(3,)) #建立執行緒,後面引述要用tuple傳入 print('Start') t.start() #執行 print('End') ``` 輸出結果 ``` Start Let's sleep Mon Feb 1 15:40:30 2021 End Wake up Mon Feb 1 15:40:33 2021 ``` 可以看到當sleep函式在睡覺的時候,CPU不會等待主執行緒睡覺,會自動切換到另一個執行緒去印出End,印完後再回到主執行緒,接著印出Wake up。 4. `Multithreading`(多執行緒): >**在同一個Process中,建立多個Thread同時執行任務**,藉此來提升效率。 > >**當主執行緒(Main Thread)閒置或等待時,CPU就會切換到另一個執行緒(Thread)執行其它的工作,完成後再回到主執行緒(Main Thread)繼續往下執行。** 5. `Coroutine`(**協程**): >是一種**用戶態的輕量級線程**,可以想成一個線程裡面可以有多個協程,而**協程的調度完全由使用者控制** > >Coroutine 一次只會執行一個 Job ,會透過**頻繁切換 Job**達到類似**異步**的效果,需要的資源比開新的 Thread 少。 > >Coroutine 任務**類似平常大家寫的 function,但又不像一般的 function,Coroutine 是可以隨時暫停執行,再從暫停的地方恢復執行的 function。** > >在 Python 中的 **async, await, yield, generator**…等,就是 Coroutine 很好的應用。 **!! 總結流程<重要> !!** >程式碼(Program)寫好之後執行,會被轉成機器看得懂的machine code,接著,**作業系統會分配記憶體,讓machine code載入到記憶體內,產生process。** **不同的Process之間不會共享資源(ex:記憶體、變數)**,此時,程式碼尚未開始被執行,**Process要被分派給CPU,才可以讓CPU執行Process內的任務,** 而一個CPU只能執行一個Process的任務,所以才會演變成多核心(CPU核心數愈多)的電腦,可以更多工,一次處理更多任務。**CPU執行Process內的任務靠的就是Process內的Thread。同一個Process底下的Thread可以共用該Process的資源(ex:記憶體、變數),此外Thread彼此之間也會共享資源。** 當Process分派給CPU之後,Process上的**Thread會跟CPU索取CPU時間,此時CPU才會開始執行Thread內的任務**。 > >所以,**Process**是作業系統分配**記憶體資源**的對象,而 **Thread** 是作業系統分配 **CPU 時間**的對象,有 CPU 時間,才可以執行 Thread 上的任務。 以蓋房子當作例子: **建設公司 <–> 作業系統 磚塊、水泥、鋼筋、怪手 <–> 記憶體資源 建案 <–> Process 建築工人 <–> Thread** ### GIL(Global Interpreter Lock) >In **CPython**, **the global interpreter lock, or GIL, is a mutex that protects access to Python objects, preventing multiple threads from executing Python bytecodes at once**. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.) >**Mutex(互斥鎖): 是一種用於多執行緒編程中,防止兩條執行緒同時對同一公共資源(比如全域變數)進行讀寫的機制。** 白話來說,在執行 multiple threads 時,CPython memory 會有 thread-safe 的問題,所以**在 Python Source Code 直譯成 bytecodes 時增加 GIL (Global Interpreter Lock) 的全域鎖。用於確保在 Python 運行時僅運行一個 Thread 來保證 Thread-safe。** >Therefore, with only one thread running at a time, GIL ensures there will never be race conditions. > >In fact, a Python process cannot run threads in parallel but it can run them concurrently through context switching during I/O bound operations. :fire: - **GIL鎖是一個互斥鎖(或鎖),它只允許一個線程(thread)持有 Python 解釋器的控制權。** - **GIL鎖會確保不會有race condition的問題!!** - **CPU-bound的任務,GIL鎖會阻礙multiple-thread的效率,但IO-bound不受影響** - **因為有GIL鎖,所以只能達到Concurrency(切換thread,同一時間不會執行多個thread),不能達到parallelism(真正的非同步,同一時間可以執行多個thread)** - **若不想用GIL鎖,解決方法是採用multiple-process(但開銷會較大)** - **GIL鎖只存在於CPython中,其他直譯器實作(Jython, PyPy...)則沒有此問題** https://towardsdatascience.com/multithreading-multiprocessing-python-180d0975ab29 https://towardsdatascience.com/python-gil-e63f18a08c65 何謂Thread Safe? >當多執行緒同時運行,並對同一資源進行讀寫操作的修改時,必須保證其執行緒與執行緒間不會發生衝突,和數據修改不會發生錯誤,稱為 thread-safe。 **每一個 Python 線程,在 CPython 解釋器中執行時,都會先鎖住自己的線程,阻止別的線程執行。等待該線程執行完畢後再釋放GIL,供下一個線程使用。** * [[Python爬蟲教學]善用多執行緒(Multithreading)提升Python網頁爬蟲的執行效率](https://www.learncodewithmike.com/2020/11/multithreading-with-python-web-scraping.html) * [Python 多執行緒 threading 模組平行化程式設計教學](https://blog.gtwang.org/programming/python-threading-multithreaded-programming-tutorial/) * [【Python教學】淺談 GIL & Thread-safe & Atomic operation](https://www.maxlist.xyz/2020/03/15/gil-thread-safe-atomic/) ### !! 死鎖(Deadlock)條件,以及如何解死鎖 **何謂Deadlock(死鎖):** >意思是系統中存在**一組 process 陷入互相等待對方所擁有的資源的情況,造成所有的 process 無法往下執行,使得 CPU 利用度大幅降低。** ![](https://i.imgur.com/Ycuo1aJ.png) 須符合以下四項充要條件才會發生deadlock: 1. `Mutual exclusion(互斥)`:資源在**同一時間內,最多只允許一個process使用(不允許≥2個processes同時使用)**,其它欲使用此resource的process必須wait,直到該process釋放resource為止 2. `Hold and wait(持有並等待)`:**process持有部分資源且又在等待其它processes所持有的資源** 3. `No preemption(不可強取豪奪)`:**process 不可以任意強奪其他 process 所持有的資源,除非其自願釋放** 4. `Circular waiting(循環等待)`:系統中存在**一組 processes P=P0,P1,…,Pn,其中 P0 等待 P1 所持有的資源 ... Pn 等待 P0 所持有的資源,形成循環式等待。**(因此,**deadlock不會存在於single process環境中**) Deadlock 三種處理方法: 1. `Deadlock prevention`: **打破四項條件之一**,即可保證 Deadlock 永不發生 * 打破 Mutual exclusion:為某些資源與生俱來之性質,無法打破。 * 打破 Hold and wait,有兩種協定: * 協定一: **除非 proces 可以一次取得所有工作所需的資源,才允許持有資源。** * 協定二: process 執行之初可持有部分資源,但**要再申請資源前,要先釋放手中所有資源。** * 打破 Preemption : **process可以搶奪waiting process所持有的Resource,雖然不會發生deadlock但有可能造成starvation** * 打破 Circular waiting,有兩個步驟: * 步驟一: 給予每個Resource唯一的(unique)資源編號(ID) * 步驟二: 規定process需依資源編號遞增的方式提出申請 ``` eg. 持有 申請 1 R1 → R3 ok 2 R3 → R1 需先釋放R3,才可申請R1 3 R1,R5 → R3 釋放R5, 留R1, 即可申請R3 ``` 2. `Deadlock avoidance`: 當process提資源申請(Request)時,則OS需依據下列資訊: 1. 系統目前**可用的資源數量(Available)** 2. 各process對資源的**需求量(Need or Max)** 3. 各process**目前持有的資源量(Allocation)** 執行**Banker's Algorithm**(內含Safety Algorithm)判斷系統 **若** 核准後,是否處於Safe state,若是,則核准申請,否則(處於unsafe state),則否決此次申請,Process則等待依段時間後再重新申請 3. `Deadlock detection & recovery` 1. 偵測死結是否存在 2. 若死結存在,則必須打破死結,恢復正常的機制 * 前兩點的 `prevention` 和 `avoidance` 保證系統不會進入 Deadlock,但資源利用度低 * 第三點的 `detection` 資源利用度較高,但若 Deadlock 需要做 recovery,則 cost 極高 ### Deadlock vs Starvation: >Deadlock是沒有希望(除非不要hold & wait,適當捨得),Starvation則是還有希望(利用Aging老化技術,提升資歷就有機會扭轉乾坤,反轉人生) ![](https://i.imgur.com/mo8kJmG.png) Starvation解決的方法: 採取類似 `Aging` 技術 **(將被搶奪的次數,列為提高優先權之依據)** [OS - Ch7 死結 Deadlock](https://mropengate.blogspot.com/2015/01/operating-system-ch7-deadlock.html) [Chapter 5 Deadlock(死結)](http://www.csie.ntnu.edu.tw/~swanky/os/chap5.htm) ### Race Condition(競爭情況) 在多 Thread (或多 CPU) 的情況之下,兩個 thread 可以共用某些變數,但是共用變數可能造成一個嚴重的問題,那就是**當兩個 thread 同時修改一個變數時,這種修改會造成變數的值可能錯誤的情況** ``` 因為多線程的程式會有競爭情況,為了避免該情況而引入了鎖定機制, 但是鎖定機制用得不好就會造成死結。 ``` ![](https://i.imgur.com/jL3pjOv.png) 原先預期會輸出12,但因為race condition的原因,最終結果卻是11 ![](https://i.imgur.com/PMDDfK4.png) 增加 **鎖(Lock)** 的機制,來控制Thread的read、write [Multithreading in Python | Set 2 (Synchronization)](https://www.geeksforgeeks.org/multithreading-in-python-set-2-synchronization/) ### 調度策略(Scheduler) 1. 長程排班程式Long-Term Scheduler (或稱 Job Scheduler) >不適用於Time-Sharing System,可調合CPU-Bound與I/O Bound processes之混合比例,且這個的執行頻率最低。 2. 短程排班程式Short-Term Scheduler (或稱 CPU Scheduler) >從Ready Queue挑選高優先度的Process,使之獲得 CPU控制權執行,Batch System和Time-Sharing都需要,Batch System和Time-Sharing均需要,這個的執行頻率最高。 3. 中程排班程式Medium-Term Scheduler >通常用在Time Sharing System,也可以調合I/O Bound與CPU Bound的比例。 ### Inter-Process Communication(IPC) * **process 與 process 間是利用 IPC(inter-process communication)的方式來溝通** * processes分為兩種,一種是不會互相影響的independent process,另一種是可以互相影響的cooperating process。 * 而實際上使用multiple process的時機大多是在處理事件或在背景做其他事。例如:在等使用者輸入時可繼續計算,或是在背景執行磁碟重組作業。 * 而常見的Inter-Process Communication有shared memory跟message Passing這兩種。 * cooperating process有資訊共享、加速運算和模組化這些好處。