Python 中用多線程進行多任務處理

2021-03-02 Python中文社區

1. GIL

熟悉python的都知道,在C語言寫的python解釋器中存在全局解釋器鎖,由於全局解釋器鎖的存在,在同一時間內,python解釋器只能運行一個線程的代碼,這大大影響了python多線程的性能。而這個解釋器鎖由於歷史原因,現在幾乎無法消除。

python GIL 之所以會影響多線程等性能,是因為在多線程的情況下,只有當線程獲得了一個全局鎖的時候,那麼該線程的代碼才能運行,而全局鎖只有一個,所以使用python多線程,在同一時刻也只有一個線程在運行,因此在即使在多核的情況下也只能發揮出單核的性能。

2. 多線程處理IO密集型任務

IO密集型任務指的是系統的CPU性能相對硬碟、內存要好很多,此時,系統運作,大部分的狀況是CPU在等I/O (硬碟/內存) 的讀/寫操作,此時CPU Loading並不高。涉及到網絡、磁碟IO的任務都是IO密集型任務。一個線程執行IO密集型任務的時候,CPU處於閒置狀態,因此GIL會被釋放給其他線程,從而縮短了總體的等待運行時間。

from concurrent.futures import ThreadPoolExecutor
from time import sleep, time
# Worker數量
N = 4
# 建立線程池
pool = ThreadPoolExecutor(max_workers=N)

2.1 定義一個IO密集型函數

該函數會「睡眠」x秒。

def io_bound_func(x):
    sleep(x)
    print("Sleep for %d seconds." % x)

2.2 使用串行的方式處理

遍歷一個列表的所有元素,執行func函數。

def process_array(arr):
    for x in arr:
        io_bound_func(x)

2.3 使用多線程處理

通過線程池的map方法,可以將同一個函數作用在列表中的所有元素上。

def fast_process_array(arr):
    for x in pool.map(io_bound_func, arr):
        pass

2.4 計算函數運行時間

串行版本的運行時間 = 1 + 2 + 3 = 6秒多線程版本的運行時間 = max(1, 2, 3) = 3秒
def time_it(fn, *args):
    start = time()
    fn(*args)
    print("%s版本的運行時間為 %.5f 秒!" % (fn.__name__, time() - start))
time_it(process_array, [1, 2, 3])

Sleep for 1 seconds.
Sleep for 2 seconds.
Sleep for 3 seconds.
process_array版本的運行時間為 6.00883 秒!

time_it(fast_process_array, [1, 2, 3])

Sleep for 1 seconds.
Sleep for 2 seconds.
Sleep for 3 seconds.
fast_process_array版本的運行時間為 3.00300 秒!

3. 多線程CPU密集型任務

CPU密集型任務的特點是要進行大量的計算,消耗CPU資源,比如計算圓周率、對視頻進行高清解碼等等,全靠CPU的運算能力。一個線程執行CPU密集型任務的時候,CPU處於忙碌狀態,運行1000個字節碼之後GIL會被釋放給其他線程,加上切換線程的時間有可能會比串行代碼更慢。

3.1 定義一個CPU密集型函數

該函數會對[1, x]之間的整數進行求和。

def cpu_bound_func(x):
    tot = 0
    a = 1
    while a <= x:
        tot += x
        a += 1
    print("Finish sum from 1 to %d!" % x)
    return tot

3.2 使用串行的方式處理

遍歷一個列表的所有元素,執行func函數。

def process_array(arr):
    for x in arr:
        cpu_bound_func(x)

3.3 使用多線程處理

通過線程池的map方法,可以將同一個函數作用在列表中的所有元素上。

def fast_process_array(arr):
    for x in pool.map(cpu_bound_func, arr):
        pass

3.4 計算函數運行時間

def time_it(fn, *args):
    start = time()
    fn(*args)
    print("%s版本的運行時間為 %.5f 秒!" % (fn.__name__, time() - start))
time_it(process_array, [10**7, 10**7, 10**7])

Finish sum from 1 to 10000000!
Finish sum from 1 to 10000000!
Finish sum from 1 to 10000000!
process_array版本的運行時間為 2.10489 秒!

time_it(fast_process_array, [10**7, 10**7, 10**7])

Finish sum from 1 to 10000000!
Finish sum from 1 to 10000000!
Finish sum from 1 to 10000000!
fast_process_array版本的運行時間為 2.20897 秒!

參考文章

1、Python中的GIL鎖:https://www.jianshu.com/p/c75ed8a6e9af

2、什麼是CPU密集型、IO密集型?:https://www.cnblogs.com/tusheng/articles/10630662.html

相關焦點

  • Python並發編程之多線程
    因此,進程只是將資源(原材料)集中到一起是資源單位,線程才是CPU具體的執行單位,一個進程中可以存在多個線程,這多個線程會共享該進程內的所有資源,因此同一個進程內開啟多線程會產生資源爭搶。為了單核實現並發已經有了多進程為何還要有多線程呢?
  • 並發和並行 | Python中實現多線程 threading 和多進程 multiprocessing
    並發和並行咱們簡單用多線程對應並發,多進程對應並行。多線程並發更強調充分利用性能;多進程並行更強調提升性能上限。我用非常簡單且不那麼嚴謹的比喻來說明。多線程一個 CPU 相當於一個學生。 如果這學生只有一項工作,那他這一周可能只需要花費兩天來做任務,剩下時間摸魚(針不搓,三點鐘飲茶先!)。因此,我們用「多線程」來讓學生實現『並發』,充分利用學生能力。giphy.com在實際情況中,多線程、高並發這些詞語更多地出現在服務端程序裡。
  • Python多任務處理:多進程篇
    CPU密集型任務的特點是要進行大量的計算,消耗CPU資源,比如計算圓周率、對視頻進行高清解碼等等,全靠CPU的運算能力。
  • python多線程/多進程
    一.概述    對於python爬蟲,默認的是使用單線程爬蟲,在批量爬數據時速度太慢。
  • Python多線程—Thread和Threading
    又到周四,科普Time,今天給大家講講Python多線程中的兩個模塊,Thread和Threading。針對兩個模塊下文中會給出一些實例,給大家加深印象。 說到Python的多線程呢,先介紹一下一定繞不過去的坑,全局解釋器鎖GIL。
  • python基礎之多線程
    線程(Thread)也叫輕量級進程,是作業系統能夠進行運算調度的最小單位,它被包涵在進程之中,是進程中的實際運作單位。線程自己不擁有系統資源,只擁有一點兒在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的全部資源。一個線程可以創建和撤消另一個線程,同一進程中的多個線程之間可以並發執行。為什麼要使用多線程線程在程序中是獨立的、並發的執行流。
  • C++ 多線程編程
    本文是一篇工程開發方面的文章,算法的落地還是需要工程代碼的依託,比如在雷射雷達感知模塊中如果既包含了傳統點雲處理做障礙物檢測,又使用深度學習做點雲目標識別,往往需要採用多線程並行處理兩個分支,再將輸出結果融合後送入跟蹤模塊,這裡簡單介紹下C++的多線程知識。線程是進程中的一個實體,是被系統獨立分配和調度的基本單位。也就是說線程是CPU可執行調度的最小單位。
  • 快速掌握用python寫並行程序,乾貨滿滿
    圖一是並行程序的示例,開始並行後,程序從主線程分出許多小的線程並同步執行,此時每個線程在各個獨立的CPU進行運行,在所有線程都運行完成之後,它們會重新合併為主線程,而運行結果也會進行合併,並交給主線程繼續處理。
  • 《Exploring in UE4》多線程機制詳解
    系統中的任務與事件4.4  其它相關技術細節五.總結一.概述多線程是優化項目性能的重要方式之一,遊戲也不例外。雖然經常能看到「遊戲不適合利用多線程優化」的言論,但我個人覺得這句話更多的是針對GamePlay,遊戲中多線程用的一點也不少,比如渲染模塊、物理模塊、網絡通信、音頻系統、IO等。下圖就展示了UE4引擎運行時的部分線程,可能比你想像的還要多一些。
  • Python multiprocess 多進程模塊
    想要在windows中運行,必須使用該的方式),但是我有另一種方法在使用線程池的時候可以不使用name_mian,最下面說。並且多線程就是開啟多個線程,每個線程之間是不會互相通信互相干擾的,適用於密集計算。
  • Python學習,gevent協程,多線程,多進程demo
    協程,線程,進程,多線程,多進程,線程池,本渣渣是徹底蒙蔽了,不過幹就是了,二話不說寫(抄)代碼就是了,抄多了就明了了,說錯了,寫多了就會了!關於geventPython通過yield提供了對協程的基本支持,但是不完全。而第三方的gevent為Python提供了比較完善的協程支持。
  • 裸機系統與多線程系統的區別
    甚至有不少人認為用中斷就能代替多任務處理,你認同嗎?裸機系統通常分成輪詢系統和前後臺系統。,則可在中斷服務程序裡面處理,如果事件要處理的事情比較多,則返回到後臺程序裡面處理。雖然事件的響應和處理是分開了,但是事件的處理還是在後臺裡面順序執行的,但相比輪詢系統,前後臺系統確保了事件不會丟失,再加上中斷具有可嵌套的功能,這可以大大的提高程序的實時響應能力。在大多數的中小型項目中,前後臺系統運用的好,堪稱有作業系統的效果。相比前後臺系統,多線程系統的事件響應也是在中斷中完成的,但是事件的處理是在線程中完成的。
  • 精心整理了100個Java多線程知識點
    在程序中,上下文切換過程中的「頁碼」信息是保存在進程控制塊(PCB)中的。PCB還經常被稱作「切換楨」(switchframe)。「頁碼」信息會一直保存到CPU的內存中,直到他們被再次使用。 上下文切換是存儲和恢復CPU狀態的過程,它使得線程執行能夠從中斷點恢復執行。上下文切換是多任務作業系統和多線程環境的基本特徵。24、Java中用到的線程調度算法是什麼?
  • Python daemon守護線程詳解
    前面不只一次提到,當程序中擁有多個線程時,主線程執行結束並不會影響子線程繼續執行。
  • Python分布式任務框架Celery的異步任務實現
    前言:比如一個用戶註冊的WEB頁面中,當提交了用戶的帳號密碼等信息之後,將會發送一封激活郵件至用戶的電子郵箱。在該場景中用戶點擊註冊按鈕後http應立即返迴響應而無需等待後續的郵件發送任務成功後才返回。這就可以用一種異步的形式去處理髮送郵件這一任務,這種異步操作可以用隊列服務來實現。
  • python logging 日誌模塊以及多進程日誌
    logger並不是直接實例化使用的,而是通過logging.getLogger(name)來獲取對象,事實上logger對象是單例模式,logging是多線程安全的,也就是無論程序中哪裡需要打日誌獲取到的logger對象都是同一個。但是不幸的是logger並不支持多進程,這個在後面的章節再解釋,並給出一些解決方案。
  • 二十四、深入Python多進程multiprocessing模塊
    「@Author:Runsen」@Author:Runsenthreading 包為 Python 提供了線程模型,而multiprocessing 包則為另一種並發模型 — 多進程模型提供了強大的解決方案
  • python 線程
    線程是作業系統能夠進行運算調度的最小單位,它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以並發多個線程,每條線程並行執行不同的任務。在Python3中實現的大部分運行任務裡,不同的線程實際上並沒有同時運行:它們只是看起來像是同時運行的。
  • 實例講解PyQt5多線程QThread的運用
    QThread類提供了一種獨立於平臺的線程管理方法。QThread對象管理程序中的一個控制線程,在run()中開始執行QThreads。默認情況下,run()通過調用exec()啟動事件循環,並在線程中運行Qt事件循環。
  • C#多線程詳解(一) Thread.Join()的詳解
    當一個程序開始運行時,它就是一個進程,進程包括運行中的程序和程序所使用到的內存和系統資源。而一個進程又是由多個線程所組成的。什麼是線程?線程是程序中的一個執行流,每個線程都有自己的專有寄存器(棧指針、程序計數器等),但代碼區是共享的,即不同的線程可以執行同樣的函數。