Python多線程解析

概述

記得前些日子傘哥發(fā)過一個微博調(diào)侃過Python由于GIL鎖的存在,所以現(xiàn)在死活想把自己和機器學(xué)習(xí)扯上關(guān)系。確實,由于這個全局解釋鎖的存在,任何時刻只有一個核在執(zhí)行Python代碼实蔽,這樣就導(dǎo)致不能充分利用多核處理器的特性。但是谨读,我們的程序也不總是在計算的局装,程序有IO密集型和CPU計算密集型。如果我們的程序需要等待用戶輸入漆腌,等待文件讀寫以及網(wǎng)絡(luò)收發(fā)數(shù)據(jù)贼邓,那計算機就會把這些等待操作放到后臺去處理,把CPU留出來用于計算闷尿。所以塑径,雖然CPU密集型的程序用Python多線程確實無法提高效率,但是如果是IO密集型的程序填具,是可以使用多線程提高效率的统舀。

接下來,讓我們通過例子一步一步了解多線程:

利用threading模塊使用多線程

Python標準庫自帶了兩個多線程模塊劳景,分別是threadingthread誉简,其中,thread是低級模塊盟广,threading是對thread的封裝闷串,一般,我們直接使用threading即可筋量。下面來看一個簡單的多線程例子:

import threading

def say_hello():
    print("Hello world!")

def main():
    for i in range(10):
        thread = threading.Thread(target=say_hello)
        thread.start()

main()

在這個例子中烹吵,我們首先定義了要多線程執(zhí)行的函數(shù)say_hello,然后我們在主函數(shù)里創(chuàng)建了10個線程,target取值是say_hi桨武,告訴線程要執(zhí)行的函數(shù)肋拔,然后我們調(diào)用start()方法吩咐線程去執(zhí)行這些線程。
這個程序最終會輸出10個Hello world!呀酸,與["Hello world!" for i in range(10)]效果一致凉蜂,那么為什么還要使用多線程呢,我們通過下面這個例子理解下多線程的意義:

import threading
import time

def say_hello():
    time.sleep(1)
    print("Hello world!")

def main():
    for i in range(10):
        thread = threading.Thread(target=say_hello)
        thread.start()

main()

在這個例子里,我們加了time.sleep(1)來模擬等待事件×裕現(xiàn)在如果用普通的循環(huán)來迭代茎杂,代碼執(zhí)行完需要至少20秒,而多線程運行只需要1秒多爆存,減少了程序整體運行的時間蛉顽。

給線程傳參和線程常用方法

在上面的代碼中蝗砾,我們并沒有給say_hello傳參數(shù)先较,在多線程里傳參很簡單,只需要這樣做就好了:

import threading

def say_hello(count, name):
    print("Hello world!", name)
    count -= 1

def main():
    name_list = ['Bob', 'Jack', 'Jone', 'Mike', 'David']
    for i in range(5):
        thread = threading.Thread(target=say_hello, args=(10, name_list[i]))
        thread.start()

main()

threading.Thread類中悼粮,常用的方法有:

  • isAlive: 檢查線程是否在運行中
  • getName: 獲取線程名稱
  • setName: 設(shè)置線程名稱
  • join:阻塞線程調(diào)用闲勺,直到線程中止
  • setDaemon:設(shè)置線程為守護線程
  • isDaemon: 判斷線程是否是守護線程

通過繼承創(chuàng)建線程

除了直接實例化threading.Thread對象,我們還可以通過繼承threading.Thread來編寫多線程的類扣猫。然后把多線程調(diào)用的函數(shù)攜程一個run方法菜循。方法如下:

import threading

class MyThread(threading.Thread):
      def __init__(self, count, name):
          super(MyThread, self).__init__()
          self.count = count
          self.name = name
     
      def run(self):
          while self.count > 10:
                print("hello", self.name) 
                self.count -= 1

線程與互斥鎖

多個線程之間 內(nèi)存是共享的,所以線程比進程輕量申尤。多個線程是可以同時訪問內(nèi)存中的數(shù)據(jù)的癌幕,如果多個線程同時修改一個對象,那這份數(shù)據(jù)可能會被破壞昧穿,Python的threading類中提供了Lock方法勺远,它會返回一個鎖對象,一般通過lock.acquire()來獲取鎖时鸵,通過lock.release()來釋放鎖胶逢,對于那種只允許一個線程操作 的數(shù)據(jù),一般把對其的操作放在lock.acquire()lock.release()中間饰潜。
無論在什么情況下初坠,我們都要保證代碼要釋放鎖,所以其他語言中一般把加鎖和釋放鎖放在try/finally語句中彭雾。在Python中碟刺,其實我們可以用上下文管理器來簡化代碼,關(guān)于上下文管理器的介紹可以參考我前面的文章:上下文管理器,這里我們可以這樣使用鎖:

with lock:
    #lock processing

下面來看一個使用互斥鎖的例子薯酝,在這個例子中半沽,我們使用了全局變量,然后創(chuàng)建10個線程蜜托,每個線程做同樣的事情抄囚,由于num是全局變量,而且每個線程都需要使用這個變量橄务,因此存在著數(shù)據(jù)爭用的問題幔托,所以,我們就需要使用互斥鎖保護這個全局變量:所有修改這個變量的線程在修改前都需要加鎖,在increment函數(shù)中重挑,我們通過with語句進行加鎖嗓化。如下所示:

import threading

lock = threading.Lock()
num = 0


def increment(count):
    global num
    while count > 0:
        with lock:
            num += 1
        count -= 1

def main():
    threads = []
    for i in range(10):
        thread = threading.Thread(target=increment,args=(100,))
        thread.start()
        threads.append(thread)

    for thread in threads:
        thread.join()

    print("except value is 1000, real value is{}".format(num))
    
main()


有興趣的讀者可以試試把鎖去掉是什么結(jié)果,實際上谬哀,我們永遠得不到正確的結(jié)果刺覆。

對于這段代碼,我們可以這樣理解:

    threads = []
    for i in range(10):
        thread = thread.Threading(target=increment, args=(100,))
        thread.start()
        threads.append(thread)
 
    for thread in threads:
        thread.join()

第一個for循環(huán)史煎,意思是吩咐十個線程去做target里面的事谦屑,執(zhí)行完第一個for循環(huán)后就吩咐完了,但僅僅是只是吩咐完了,target里面的任務(wù)沒有執(zhí)行完篇梭,因為不知道是否執(zhí)行完氢橙,所以還需要創(chuàng)建一個列表把他們都保存起來。對于第二個for循環(huán)恬偷,如果在第一個循環(huán)里 他們的target已經(jīng)執(zhí)行完了悍手,那后面直接就join,不用等待袍患,遇到有的線程沒有執(zhí)行完的坦康,join 就阻塞調(diào)用,直到這些線程執(zhí)行完诡延。

線程安全隊列queue

隊列是線程間最常用的交換數(shù)據(jù)的形式滞欠,queue模塊實現(xiàn)了線程安全的隊列,有三種類型的隊列:

  • queue Queue:FIFO(先進先出) 的隊列孕暇。最常用的隊列仑撞!
  • queue LifoQueue: LIFO(后進先出)的隊列,最后加入隊列的元素最先取出
  • queue PriorityQueue: 優(yōu)先級隊列妖滔,隊列中的元素根據(jù)優(yōu)先級排序隧哮。

下面是Queue類常用的方法:

  • empty: 判斷隊列是否為空
  • full: 判斷隊列是否已滿
  • put: 向隊列中添加元素
  • get: 從隊列中取出元素
  • put_nowait: 非阻塞 向隊列中添加元素
  • get_nowait: 非阻塞 從隊列中取出元素
  • join:阻塞等待,直到所有任務(wù)完成

來看一個官方給的多線程模型:

def worker():
    while True:
    item = q.get()
    do_work()
    q.task_done()

q = Queue()

for i in range(thread_number):
    t = Thread(target=worker)
    t.daemon = True
    t.start()

for item in source():
    q.put(item)  

q.join()

之后會有一個線程池的例子運用Queue隊列座舍。寫完后放鏈接沮翔!待續(xù)!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末曲秉,一起剝皮案震驚了整個濱河市采蚀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌承二,老刑警劉巖榆鼠,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異亥鸠,居然都是意外死亡妆够,警方通過查閱死者的電腦和手機识啦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來神妹,“玉大人颓哮,你說我怎么就攤上這事⊥臆” “怎么了冕茅?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蛹找。 經(jīng)常有香客問我姨伤,道長,這世上最難降的妖魔是什么熄赡? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任姜挺,我火速辦了婚禮齿税,結(jié)果婚禮上彼硫,老公的妹妹穿的比我還像新娘。我一直安慰自己凌箕,他們只是感情好拧篮,可當(dāng)我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著牵舱,像睡著了一般串绩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芜壁,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天礁凡,我揣著相機與錄音,去河邊找鬼慧妄。 笑死顷牌,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的塞淹。 我是一名探鬼主播窟蓝,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼饱普!你這毒婦竟也來了运挫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤套耕,失蹤者是張志新(化名)和其女友劉穎谁帕,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冯袍,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡匈挖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片关划。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡小染,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贮折,到底是詐尸還是另有隱情裤翩,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布调榄,位于F島的核電站踊赠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏每庆。R本人自食惡果不足惜筐带,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缤灵。 院中可真熱鬧伦籍,春花似錦、人聲如沸腮出。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胚嘲。三九已至作儿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間馋劈,已是汗流浹背攻锰。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留妓雾,地道東北人驱还。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓漱竖,卻偏偏與公主長得像投队,于是被迫代替她去往敵國和親哆料。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容