Python 爬蟲進階篇——多線程

本次推文介紹一下多線程。不過值得注意的是,不能濫用多線程,多線程爬蟲請求內容速度過快,可能會導致服務器過載,或者是IP被封禁。為了避免這一問題,我們在使用多線程爬蟲的時候需要設置一個delay時間,用于請求同一域名時的最小時間間隔。

線程和進程如何工作

當程序在運行時,就會創建包含代碼和狀態的進程。這些進程通過一個或者多個CPU來執行。不過同一時刻每個CPU只會執行一個進程,然后在不同進程之間快速切換,這樣就感覺多個程序同時運行。同理,在一個進程中,程序的執行也是在不同線程間進行切換的,每個線程執行程序的不同部分。這就意味著一個線程在等待執行時,進程會切換到其他的線程執行,這樣可以避免浪費CPU時間。

Threading線程模塊

在Python標準庫中,使用threading模塊來支持多線程。Threading模塊對thread進行了封裝,絕大數情況,只需要使用threading這個模塊。使用起來也非常簡單:

t1=threading.Thread(target=run,args=("t1",)) 創建一個線程實例
# target是要執行的函數名(不是函數),args是函數對應的參數,以元組的形式存在
t1.start() 啟動這個線程實例。

普通創建方式

線程的創建很簡單,如下:

import threading
import time

def printStr(name):
    print(name+"-python知識學堂")
    s=0.5
    time.sleep(s)
print(name+"-python知識學堂")

t1=threading.Thread(target=printStr,args=("你好!",))
t2=threading.Thread(target=printStr,args=("歡迎你!",))
t1.start()
t2.start()

自定義線程

本質是繼承threading.Thread,重構Thread類中的run方法

import threading
import time

class testThread(threading.Thread):
    def __init__(self,s):
        super(testThread,self).__init__()
        self.s=s

    def run(self):
        print(self.s+"——python")
        time.sleep(0.5)
        print(self.s+"——知識學堂")

if __name__=='__main__':
    t1=testThread("測試1")
    t2=testThread("測試2")
    t1.start()
    t2.start()

守護線程
使用setDaemon(True)把子線程都變成主線程的守護線程,因此當主線程結束后,子線程也會隨之結束。也就是說,主線程不等待其守護線程執行完成再去關閉。

import threading
import time

def run(s):
print(s,"python")
time.sleep(0.5)
print(s,"知識學堂")

if name == "main":
t=threading.Thread(target=run,args=("你好!",))
t.setDaemon(True)
t.start()
print("end")

結果:

你好! python

end

當主線程結束后,守護線程不管有沒有結束,都自動結束。

主線程等待子線程結束
使用join方法,讓主線程等待子線程執行。如下:

import threading
import time

def run(s):
print(s,"python")
time.sleep(0.5)
print(s,"知識學堂")

if name == "main":
t=threading.Thread(target=run,args=("你好!",))
t.setDaemon(True)
t.start()
t.join()
print("end")

結果:

你好! python

你好! 知識學堂

end

以上是多線程的幾種簡單的用法,那么threading模塊還有做什么呢?請往下看。

Lock 鎖
其實在介紹diskcache緩存的時候也介紹過鎖的相關內容,其實不難理解為啥多線程中也會出現鎖的概念,當沒有保護共享資源時,多個線程在處理同一資源時,可能會出現臟數據,造成不可以預期的結果,即線程不安全。

如下示例出現不可預期的結果:

import threading

price=0
def changePrice(n):
global price
price=price+n
price=price-n

def runChange(n):
for i in range(2000000):
changePrice(n)

if name == "main":
t1=threading.Thread(target=runChange,args=(5,))
t2=threading.Thread(target=runChange,args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(price)

理論上的結果為0,但是每次運行的結果可能都是不一樣的。

所以這個時候就需要鎖去處理了,如下:

import threading
import time
from threading import Lock

price=0
def changePrice(n):
global price
lock.acquire() #獲取鎖
price=price+n
print("price:"+str(price))
price=price-n
lock.release() #釋放鎖

def runChange(n):
for i in range(2000000):
changePrice(n)

if name == "main":
lock=Lock()
t1=threading.Thread(target=runChange,args=(5,))
t2=threading.Thread(target=runChange,args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(price)

結果值與理論值是一致的。鎖的意義在于每次只允許一個線程去修改同一數據,以保證線程安全。

信號量
BoundedSemaphore類,同時允許一定數量的線程更改數據,如下:

import threading
import time

def work(n):
semaphore.acquire()
print("序號:"+str(n))
time.sleep(1)
semaphore.release()

if name == "main":
semaphore=threading.BoundedSemaphore(5)
for i in range(100):
t=threading.Thread(target=work,args=(i+1,))
t.start()

active_count獲取當前正在運行的線程數

while threading.active_count()!=1:
pass
else:
print("end")

結果為:每5次打印停頓一下,直到結束。

GIL全局解釋器鎖
說到多線程,不得不提一下GIL。GIL的全稱是Global Interpreter Lock(全局解釋器鎖),這是python設計之初,為了數據安全所做的決定。某個線程想要執行,必須先拿到GIL,并且在一個進程中,GIL只有一個。只有拿到GIL的線程,才能進入CPU執行。GIL只在cpython中才有,因為cpython調用的是c語言的原生線程,所以他不能直接操作cpu,只能利用GIL保證同一時間只能有一個線程拿到數據。而在pypy和jpython中是沒有GIL的。

總結
本篇文章介紹了多線程的用法,要根據實際的情況去使用。多線程編程,容易發生沖突,必須用鎖加以隔離,又得小心發生死鎖。由于Python的設計時有GIL全局鎖,導致多線程無法利用多核,使得在多線程并發的情況下并不理想。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容