前提
- 我是參考 Github Python 100 天的文章寫的,再結合自己的小練習,總結
- 最近在面大廠,發現許多大廠都會問 Python 的多執行緒、多程式,所以我覺得很有必要總結學習下
什麼是程式
作業系統中執行的一個程式,類似微信、QQ,每個程式都是一個程式
概念
- 它是 CPU 最小資源分配單元
- 作業系統會給程式分配記憶體空間,每個程式都會有自己的地址空間、資料棧以及其他用於跟蹤程式執行的輔助資料
- 作業系統管理所有程式的執行,為它們合理的分配資源
fork、spawn
- 程式可以通過 fork、spawn 的方式來建立新的程式來執行其他任務
- 不過新的程式有自己獨立的記憶體空間、資料棧
- 因此不同程式間需要通過通訊機制(IPC)來實現資料共享
常見通訊機制
- 管道
- 訊號
- 套接字
- 共享記憶體區
什麼是執行緒
- 程式中可以擁有多個併發的執行線索
- 它是 CPU 最小排程的執行單元
特點
- 同一個程式下的執行緒共享相同的上下文
- 相對於程式來說,執行緒間的資訊共享和通訊更加容易
單核 CPU 系統注意事項
- 真正的併發是不可能的
- 因為在某個時刻,CPU 只能執行唯一的一個執行緒
- 多個執行緒共享了 CPU 的執行時間
多執行緒的好處
- 提升程式的效能和改善使用者體驗
- 今天日常使用的軟體幾乎都用到了多執行緒
多執行緒的壞處
- 站在其他程式的角度,多執行緒的程式對其他程式並不友好,因為它佔用了更多的 CPU 執行時間,導致其他程式無法獲得足夠的 CPU 執行時間
- 編寫和除錯多執行緒的程式對開發者要求較高
Python 實現併發程式設計的方式
- 多程式
- 多執行緒
- 多程式+多執行緒
Python 中的多程式
Linux 下的 fork 函式
- Linux 作業系統上提供了 fork() 系統呼叫來建立程式
- 呼叫 fork() 函式的是父程式
- 建立的是子程式
- 子程式是父程式的拷貝
- 但子程式有自己的 PID
- fork() 函式非常特殊,它會返回兩次,父程式中呼叫 fork() 會返回子程式的 PID,子程式中呼叫 fork() 得到的都是0
Python 提供的 fork 函式
os 模組提供了 fork()
Window 下沒有fork()的呼叫
- 實現跨平臺的多程式變成,可以使用 multiprocessing 模組的 Process 類來建立子程式
- 還提供了更高階的封裝,例如批量啟動程式的程式池 pool、用於程式間同喜你的佇列 Queue 和管道 Pipe
使用多程式和不使用多程式的區別(寫程式碼)
不使用多程式
from random import randint from time import time, sleep def download_task(filename): print('開始下載%s...' % filename) time_to_download = randint(5, 10) sleep(time_to_download) print('%s下載完成! 耗費了%d秒' % (filename, time_to_download)) def main(): start = time() download_task('Python從入門到住院.pdf') download_task('Peking Hot.avi') end = time() print('總共耗費了%.2f秒.' % (end - start)) if __name__ == '__main__': main()
執行結果
開始下載Python從入門到住院.pdf...
Python從入門到住院.pdf下載完成! 耗費了10秒
開始下載Peking Hot.avi...
Peking Hot.avi下載完成! 耗費了9秒
總共耗費了19.02秒.
可以看到需要先等第一個檔案下載完才能下載第二個檔案,效率很低
使用多程式
from random import randint from time import time, sleep from multiprocessing import Process def download_task(filename): print('開始下載%s...' % filename) time_to_download = randint(5, 10) sleep(time_to_download) print('%s下載完成! 耗費了%d秒' % (filename, time_to_download)) def main2(): start = time() p1 = Process(target=download_task,args=("Python從入門到住院.pdf",)) p1.start() p2 = Process(target=download_task, args=("Peking Hot.avi",)) p2.start() p1.join() p2.join() end = time() print('總共耗費了%.2f秒.' % (end - start)) if __name__ == '__main__': main2()
執行結果
開始下載Python從入門到住院.pdf...
開始下載Peking Hot.avi...
Python從入門到住院.pdf下載完成! 耗費了6秒
Peking Hot.avi下載完成! 耗費了10秒
總共耗費了10.17秒.
兩個任務同時執行,總耗時不再是兩個任務的時間總和
知識點
- Process:通過 Process 類建立程式物件
- target:通過 target 引數傳入一個函式名來表示程式啟動後要執行的程式碼
- args:是一個元組,代表傳遞給函式的引數列表
- start:Process 的 start() 方法來啟動程式
- join:Process 的 join() 方法表示等待程式執行結束,才會往下執行
Python 中的多執行緒
前言
推薦 threading 模組來實現多執行緒程式設計,它提供了更好的物件導向封裝
多執行緒的實現方式
#!/usr/bin/env python # -*- coding: utf-8 -*- """ __title__ = __Time__ = 2021/3/19 18:17 __Author__ = 小菠蘿測試筆記 __Blog__ = https://www.cnblogs.com/poloyy/ """ from random import randint from threading import Thread from time import time, sleep def download_task(filename): print('開始下載%s...' % filename) time_to_download = randint(5, 10) sleep(time_to_download) print('%s下載完成! 耗費了%d秒' % (filename, time_to_download)) def main3(): start = time() p1 = Thread(target=download_task,args=("Python從入門到住院.pdf",)) p1.start() p2 = Process(target=download_task, args=("Peking Hot.avi",)) p2.start() p1.join() p2.join() end = time() print('總共耗費了%.2f秒.' % (end - start)) if __name__ == '__main__': main3()
執行結果
開始下載Python從入門到住院.pdf...
開始下載Peking Hot.avi...
Peking Hot.avi下載完成! 耗費了6秒
Python從入門到住院.pdf下載完成! 耗費了8秒
總共耗費了8.01秒.
一樣執行效率高很多
自定義執行緒類
#!/usr/bin/env python # -*- coding: utf-8 -*- """ __title__ = __Time__ = 2021/3/19 18:17 __Author__ = 小菠蘿測試筆記 __Blog__ = https://www.cnblogs.com/poloyy/ """ from random import randint from threading import Thread from time import time, sleep class downLoadTask(Thread): def __init__(self,filename): super().__init__() self.filename = filename def run(self) -> None: print('開始下載%s...' % self.filename) time_to_download = randint(5, 10) sleep(time_to_download) print('%s下載完成! 耗費了%d秒' % (self.filename, time_to_download)) def main3(): start = time() p1 = downLoadTask("Python從入門到住院.pdf") p2 = downLoadTask("Peking Hot.avi") p1.start() p2.start() p1.join() p2.join() end = time() print('總共耗費了%.2f秒.' % (end - start)) if __name__ == '__main__': main3()
執行結果
開始下載Python從入門到住院.pdf...
開始下載Peking Hot.avi...
Peking Hot.avi下載完成! 耗費了6秒
Python從入門到住院.pdf下載完成! 耗費了9秒
總共耗費了9.00秒.
也是一樣的高效執行
重點知識:start 和 run 方法的區別
比較點 | start | run |
作用 | 啟動執行緒,獲取 CPU 時間片 | 執行執行緒指定的程式碼塊 |
執行緒狀態 | 可執行狀態 | 執行狀態 |
呼叫次數 | 一個執行緒只能呼叫一次 | 可以重複呼叫 |
執行執行緒 | 建立了一個子執行緒,執行緒名是自己命名的 | 在主執行緒中呼叫了一個普通函式 |
注意點 | 想用多執行緒,必須呼叫 start() |
Python 中的協程
什麼是協程
Python 中,單執行緒+非同步 I/O 的程式設計模型
協程的優勢
- 極高的執行效率
- 子程式切換不是執行緒切換,而是由程式本身控制,沒有執行緒切換的開銷
- 不需要多執行緒的所機制,只有一個執行緒,所以不存在同時寫變數衝突,在協程中控制共享資源不用加鎖,只需要判斷狀態就好了,所以執行效率比多執行緒高很多
重點
要充分利用 CPU 的多核特性,應該使用多程式+協程的方式
待更新