Python - 多線程、多進程


前提

  • 我是參考 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 = Thread(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 的多核特性,應該使用多進程+協程的方式

 

待更新

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM