文章目錄
- 前言
- 基礎知識
- GIL
- 多執行緒
- 創建Thread 物件
- 自定義類繼承 Thread
前言
網路爬蟲程式是一種 IO 密集型(頁面請求,檔案讀取)程式,會阻塞程式的運行消耗大量時間,而 Python 提供多種并發編程方式,能夠在一定程度上提升 IO 密集型程式的執行效率,再開始之前你要先了解以下概念!
基礎知識
并發:一段時間內發生某些事情,在單核 CPU 中,執行多個任務是以并發的方式運行的,由于只有一個核心處理器,CPU 把一個時間段劃分成幾個時間區間,各個任務只會在自己的時間區間執行,如果在自己的時間階段沒有完成任務,就會切換到下一個任務,由于各個時間段很短,切換頻繁,所以給人的感覺是“同時”運行,
并行:同一時刻進行發生某些事情,在多核 CPU 中,是能夠實作真正“同時”運行的,當一個 CPU 執行某個行程時,其他的 CPU 可以執行其他行程,兩個行程互不搶占 CPU 資源,

同步:同步中各個任務不是獨自運行的,任務之間有交替順序,只有前一個任務完成后,后面的任務才能夠開始運行,
異步:異步中各個任務可以獨自運行,任務之間不會互相影響,
在爬蟲程序中,異步相當于打開一個網頁之后,不需要等待頁面加載完成,繼續打開新的網頁,同步相當于打開一個網頁,要等待它完全加載完才打開下一個網頁,
提高爬蟲速度的三種方式:多執行緒、多行程、協程,先來了解一下什么是行程,執行緒,協程?
行程:行程是一個可以獨立運行的程式單位, 它是執行緒的集合,是由一個或多個執行緒構成的,
執行緒:是作業系統進行運算調度的最小單位,也是行程中的一個最小運行單元,
協程:協程是比執行緒更小的執行單元,可以說是一種輕量級的執行緒,執行緒的調度是在作業系統中進行的,而協程調度則是在用戶空間進行的,它相對于執行緒的優點是切換成本更低,
GIL
GIL 全稱(Global Interpreter Lock,全域解釋器鎖)在 Python 多執行緒下,每個執行緒的執行方式如下:
獲取 GIL >>> 執行對應執行緒的代碼 >>> 釋放 GIL
一個執行緒想要執行,先要拿到 GIL,可以把 GIL 看作是許可證,并且在一個 Python 行程中,GIL 只有一個,拿到許可證才能夠執行執行緒,這樣就會導致,即使是多核條件下,一個 Python 行程下的多個執行緒,同一時刻也只能執行一個執行緒,
對于 IO 密集型(頁面請求等) 任務來說,這個問題影響并不大;而對于 CPU密集型 任務來說,由于 GIL 的存在,多執行緒總體的運行效率相比可能反而比單執行緒更低,
多執行緒
多執行緒的應用場景: I/O 密集型 的程式,如
- 資料庫請求
- 頁面請求
- 讀寫檔案
由于 GIL 的原因,全域只允許同一時間執行一個執行緒意味著: 為了保證各個執行緒都能完成自身的任務,需要頻繁的進行 執行緒切換 操作,
Python 中實作多執行緒編程需要用到 threading 模塊,我們每創建一個 Thread 物件就代表一個執行緒,每個執行緒可以去處理不同的任務,
創建 Thread 物件有 2 種方式,
- 將回呼函式作為引數,直接創建
Thread物件, - 從
threading.Thread繼承創建一個新的子類,復寫run()方法,實體化后呼叫start()方法啟動新執行緒,
創建Thread 物件
threading.Thread(target=None, name=None, args=(), kwargs=None, *, daemon=None)
- target:指定要被
run()方法呼叫的可呼叫物件,默認為None,表示不呼叫任何函式, - name:執行緒名,默認情況下,單一名稱以
“Thread-N”的形式構造,其中 N 是十進制數, - args:目標呼叫的引數元組(
target的固定引數),默認為(), - kwargs:目標呼叫的關鍵字引數字典(
target的可變引數),默認值為None, - daemon:是否開啟守護執行緒,默認
MainThread(主執行緒)需要等待其他執行緒結束后才會結束,默認值為None.
import threading
import time
def block(second):
print(threading.current_thread().name, '執行緒正在運行')
# 休眠 second 秒
time.sleep(second)
print(threading.current_thread().name, '執行緒結束')
print(threading.current_thread().name, '執行緒正在運行')
for i in [1, 3]:
# 創建thread物件并指定回呼函式block,name,以及固定引數i
thread = threading.Thread(target=block, name=f'thread test {i}', args=[i])
# 開啟執行緒
thread.start()
print(threading.current_thread().name, '執行緒結束')

threading.current_thread().name 獲取當前執行緒的名稱,先簡單說一下上面代碼的邏輯,先定義函式 block,輸出當前執行緒資訊,回圈兩次創建 thread 物件,然后開啟執行緒,最后輸出執行緒結束資訊,注意各個資訊的輸出順序,在 test1、test3 執行緒結束前主執行緒就已經結束了,
自定義類繼承 Thread
現在直接在上面的例子上進行修改,使用自定義類來繼承 Thread 實作多執行緒,
import threading
import time
class TestThread(threading.Thread):
def __init__(self, name=None, second=0):
threading.Thread.__init__(self, name=name)
self.second = second
def run(self):
print(threading.current_thread().name, '執行緒正在運行')
time.sleep(self.second)
print(threading.current_thread().name, '執行緒結束')
print(threading.current_thread().name, '執行緒正在運行')
for i in [1, 3]:
thread = TestThread(name=f'thread test {i}', second=i)
# 開啟執行緒
thread.start()
print(threading.current_thread().name, '執行緒結束')

本篇只是簡單的開頭,后續將持續分享,直至掌握 Python并發爬蟲,
對于剛入門 Python 或是想要入門 Python 的小伙伴,可以通過下方小卡片聯系作者,一起交流學習,都是從新手走過來的,有時候一個簡單的問題卡很久,但可能別人的一點撥就會恍然大悟,由衷的希望大家能夠共同進步,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/344258.html
標籤:python
上一篇:CSDN熱榜、華為云博客都可用來練習Python scrapy 爬蟲
下一篇:python實作中國象棋
