Python的多線程模塊threading

概述

Python3的多線程編程中常用的兩個模塊為:_thread挤茄,threading逞刷。

推薦使用threading模塊村生。畢竟_thread模塊是相當(dāng)?shù)讓拥哪K惊暴,雖然該模塊可以讓你對線程進(jìn)行細(xì)致的管理,但由于沒有提供高級函數(shù)趁桃,所以用起來比較費(fèi)勁辽话。(輪子不如threading模塊的好使)

以下內(nèi)容將基于threading模塊展開。

創(chuàng)建線程

threading模塊支持兩種創(chuàng)建線程的方式镇辉,分別是函數(shù)方式創(chuàng)建屡穗,以及使用來包裝線程對象的方式。

""" 案例1:函數(shù)式創(chuàng)建線程 """

import threading
import time


# 線程執(zhí)行函數(shù)
def run(num):
    time.sleep(1)
    print(f'i am threading{num}')


if __name__ == '__main__':
    # 獲取開始的時間戳
    start = time.time()
    # 存放線程的列表忽肛,用于阻塞線程
    thread_list = list()
    # 創(chuàng)建4個線程村砂,并將每個創(chuàng)建好的線程對象放到thread_list中
    for i in range(1, 5):
        # 創(chuàng)建線程
        thread = threading.Thread(target=run, args=(i,))
        # 啟動該線程
        thread.start()
        thread_list.append(thread)
    
    # 只有線程全部結(jié)束,再向下執(zhí)行
    for j in thread_list:
        j.join()
    
    # 結(jié)束的時間戳
    end = time.time()
    # 打印該程序運(yùn)行了幾秒
    print(end-start)
    
# i am threading1
# i am threading3
# i am threading2
# i am threading4
# 1.0026652812957764

函數(shù)式創(chuàng)建線程是非常簡單的屹逛,只需要將實(shí)例化Thread對象就可以了础废。

target參數(shù)為線程的執(zhí)行函數(shù),args參數(shù)是一個元組罕模,里面可以存放該執(zhí)行函數(shù)的參數(shù)评腺,沒有可以省略。

需要注意的是淑掌,args元組參數(shù)里的逗號蒿讥,千萬不要忘。

""" 案例2:類繼承創(chuàng)建線程 """

import threading
import time


# 繼承Thread,轉(zhuǎn)變?yōu)榫€程類
class MyThread(threading.Thread):

    def __init__(self, threadId):
        # 必須實(shí)現(xiàn)Thread類的init方法
        super().__init__()
        self.threadId = threadId
    
    # 重寫執(zhí)行函數(shù)
    def run(self):
        time.sleep(1)
        print(f'i am thread{self.threadId}')


if __name__ == '__main__':
    # 獲取開始的時間戳
    start = time.time()
    # 存放線程的列表芋绸,用于阻塞線程
    thread_list = list()
    # 創(chuàng)建4個線程媒殉,并將每個創(chuàng)建好的線程對象放到thread_list中
    for i in range(1, 5):
        thread = MyThread(i)
        # 啟動該線程
        thread.start()
        thread_list.append(thread)
        
    # 只有線程全部結(jié)束,再向下執(zhí)行
    for j in thread_list:
        j.join()
    
    # 結(jié)束的時間戳
    end = time.time()
    # 打印該程序運(yùn)行了幾秒
    print(end-start)

# i am thread1
# i am thread3
# i am thread4
# i am thread2
# 1.0024833679199219

一般來說摔敛,線程的執(zhí)行函數(shù)是沒有返回值的廷蓉。但如果需要返回值,則可以通過定義一個全局變量來獲取马昙。當(dāng)然桃犬,如果使用的是類繼承的方式創(chuàng)建,則可以直接使用類屬性來接收返回值行楞。

守護(hù)線程

守護(hù)線程作用是為其他線程提供便利服務(wù)攒暇。只要當(dāng)前主線程中尚存任何一個非守護(hù)線程沒有結(jié)束,守護(hù)線程就全部工作敢伸,只有當(dāng)最后一個非守護(hù)線程結(jié)束扯饶,守護(hù)線程隨著主線程一同結(jié)束工作恒削。

""" 案例3:守護(hù)線程 """

import threading
import time


def run():
    # 無限循環(huán)打印
    while True:
        time.sleep(0.3)
        print('i am alive!')


def fly():
    # 打印3次
    for i in range(3):
        time.sleep(0.3)
        print('you alive!')
    print('you die...')


if __name__ == '__main__':
    # 創(chuàng)建線程1池颈,執(zhí)行函數(shù)為run
    thread1 = threading.Thread(target=run)
    # 創(chuàng)建線程2,執(zhí)行函數(shù)為fly
    thread2 = threading.Thread(target=fly)

    # 將線程1設(shè)置為守護(hù)線程
    thread1.daemon = True

    # 啟動這2個線程
    thread1.start()
    thread2.start()
    
# you alive!
# i am alive!
# you alive!
# i am alive!
# i am alive!
# you alive!
# you die...

由上述案例可看出钓丰,多線程任務(wù)中躯砰,如果子線程不是守護(hù)線程,主線程運(yùn)行完畢后會進(jìn)入停止?fàn)顟B(tài)携丁,但是主線程的占用資源沒有釋放(程序不會退出)琢歇,一直到所有的子線程全部執(zhí)行完成后才會釋放資源,退出程序梦鉴。

另外李茫,需要注意,設(shè)置守護(hù)線程時一定要在start方法之前設(shè)置肥橙。

threading模塊的常用方法

關(guān)于這些方法魄宏,值得一提的是,threading模塊仍然支持 Python 2.x 的以駝峰命名實(shí)現(xiàn)的方法存筏,不過筆者還是建議使用下劃線的方法宠互。(當(dāng)然,如果是為了兼容 Python 2.x 椭坚,那就請忽略筆者的建議予跌。)

# 啟動線程
threading.start()

# 等待至該線程中止,可以達(dá)到資源獨(dú)占的作用
threading.join([time])

# 返回線程是否存活的
threading.is_alive()

# 返回當(dāng)前的Thread對象
threading.current_thread()

# 設(shè)置守護(hù)線程
threading.setDaemon(True)

# 代表線程活動的方法,可以在子類型里重載
threading.run()

# 返回一個包含正在運(yùn)行的線程的list
threading.enumerate()

# 返回正在運(yùn)行的線程數(shù)量善茎,等價于len(threading.enumerate())
threading.active_count()

# 設(shè)置線程名
threading.setName()

# 返回線程名
threading.getName()

鎖對象與共享變量

多線程同時訪問同一變量時券册,會產(chǎn)生共享變量的問題,造成變量沖突。也就是當(dāng)線程需要共享數(shù)據(jù)時烁焙,可能存在數(shù)據(jù)不同步的問題略吨。這時候可以使用threading.Lock來把線程中的變量鎖定,使用完再釋放考阱。

# 創(chuàng)建鎖對象
lock = threading.Lock()

# 獲取鎖翠忠,用于線程同步
lock.acquire()

# 釋放鎖,開啟下一個線程
lock.release()

具體使用可以參考下列案例乞榨。

""" 案例4:線程鎖 """

import threading


def func():
    # 引用全局變量num
    global num
    # 為num循環(huán)加1
    for i in range(1, 1000001):
        # 為下面1行代碼上鎖
        lock.acquire()
        num += 1
        # 解鎖上1行代碼
        lock.release()
    # threading.current_thread().name為打印線程名
    print(threading.current_thread().name, num)


if __name__ == '__main__':
    # 計數(shù)君
    num = 0

    # 創(chuàng)建鎖對象
    lock = threading.Lock()

    # 創(chuàng)建2個線程
    thread1 = threading.Thread(target=func)
    thread2 = threading.Thread(target=func)

    # 為這兩個Thread對象(線程)起名
    thread1.name = 'thread_1'
    thread2.name = 'thread_2'

    # 啟動這2個線程
    thread1.start()
    thread2.start()

    # 等待線程結(jié)束
    thread1.join()
    thread2.join()

    print('程序運(yùn)行完畢秽之!')

# thread_1 1655942
# thread_2 2000000
# 程序運(yùn)行完畢!

本文中筆者有提到吃既,可以通過使用全局變量來接收返回值考榨。案例4的代碼就相當(dāng)于是使用了num接收函數(shù)func的返回值,即num加1000000的值鹦倚。

然后河质,這個案例對線程鎖的意義在哪?實(shí)際上震叙,當(dāng)range里的數(shù)字比較小時掀鹅,對程序本身的影響并不大。然而媒楼,當(dāng)range里的數(shù)字過大時乐尊,也就是代表計算密集度非常大時,就會出現(xiàn)數(shù)據(jù)的混亂划址。

""" 去掉線程鎖時的打印結(jié)果 """

# thread_1 1101612
# thread_2 1367565
# 程序運(yùn)行完畢扔嵌!

由上述案例便可看出,線程鎖的重要性夺颤。當(dāng)然痢缎,可能有些讀者會問,為什么案例4第一條打印的是1655942而不是1000000世澜?其實(shí)呢独旷,只需要在多線程的角度思考一下就可以相通了。

最后還需要提一下宜狐,線程鎖不僅僅只能通過threading.Lock()創(chuàng)建势告,還可以使用threading.RLock()多重鎖,用法和threading.Lock()相同抚恒。因為threading.Lock()在線程中必須等待鎖釋放后(release)才能再次上鎖咱台。而threading.RLock在同一線程中可用被多次acquire。需要注意的是俭驮,在threading.RLock中acquire和release必須成對出現(xiàn)回溺。

死鎖

在線程共享多個資源的時候春贸,如果兩個線程分別占有一部分資源并且同時等待對方的資源,這種情況便會造成死鎖遗遵。

在Python中的死鎖一般都是由于threading.Lock的acquire使用不合理導(dǎo)致的萍恕。情景就類似于哲學(xué)家吃飯的問題:由于服務(wù)員的疏忽,導(dǎo)致2個哲學(xué)家手中分別拿到的是2把叉子和2把餐刀车要,其中一個哲學(xué)家說允粤,你給我叉子,我給你餐刀翼岁,另一個哲學(xué)家則說类垫,你給我餐刀,我給你叉子......

""" 案例5:死鎖 """

import threading
import time


# 哲學(xué)家a
def philosopher_a():
    if lock_b.acquire():
        print('我有餐刀')
        print('給我叉子')
        time.sleep(0.5)
        if lock_a.acquire():
            print('我給你餐刀')
            lock_a.release()
        lock_b.release()


# 哲學(xué)家b
def philosopher_b():
    if lock_a.acquire():
        print('我有叉子')
        print('給我餐刀')
        time.sleep(0.5)
        if lock_b.acquire():
            print('我給你叉子')
            lock_b.release()
        lock_a.release()


if __name__ == "__main__":
    # 創(chuàng)建2個鎖對象
    lock_a = threading.Lock()
    lock_b = threading.Lock()

    # 創(chuàng)建2個線程
    thread1 = threading.Thread(target=philosopher_a)
    thread2 = threading.Thread(target=philosopher_b)

    # 啟動這2個線程
    thread1.start()
    thread2.start()

上述代碼一旦運(yùn)行琅坡,就會使程序掛死悉患,原理很簡單,就是2個鎖在互相等待對方解鎖榆俺。你不解鎖售躁,我不解鎖,那就停住咯茴晋。是不是像極了你和女朋友吵架陪捷,你不道歉,我不道歉晃跺,那就誰也不理誰咯揩局。

所以說,在程序設(shè)計中掀虎,一定要嚴(yán)格分析鎖定的數(shù)據(jù),避免死鎖的問題付枫,將死鎖扼殺在搖籃里才是最安全的烹玉。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市阐滩,隨后出現(xiàn)的幾起案子二打,更是在濱河造成了極大的恐慌,老刑警劉巖掂榔,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件继效,死亡現(xiàn)場離奇詭異,居然都是意外死亡装获,警方通過查閱死者的電腦和手機(jī)瑞信,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來穴豫,“玉大人凡简,你說我怎么就攤上這事逼友。” “怎么了秤涩?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵帜乞,是天一觀的道長。 經(jīng)常有香客問我筐眷,道長黎烈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任匀谣,我火速辦了婚禮怨喘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘振定。我一直安慰自己必怜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布后频。 她就那樣靜靜地躺著梳庆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪卑惜。 梳的紋絲不亂的頭發(fā)上膏执,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天,我揣著相機(jī)與錄音露久,去河邊找鬼更米。 笑死,一個胖子當(dāng)著我的面吹牛毫痕,可吹牛的內(nèi)容都是我干的征峦。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼消请,長吁一口氣:“原來是場噩夢啊……” “哼栏笆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起臊泰,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤蛉加,失蹤者是張志新(化名)和其女友劉穎仑性,沒想到半個月后记焊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凑保,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蔬浙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年剥扣,在試婚紗的時候發(fā)現(xiàn)自己被綠了狈惫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锨亏。...
    茶點(diǎn)故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡秫逝,死狀恐怖贺辰,靈堂內(nèi)的尸體忽然破棺而出户盯,到底是詐尸還是另有隱情嵌施,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布莽鸭,位于F島的核電站吗伤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏硫眨。R本人自食惡果不足惜足淆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望礁阁。 院中可真熱鬧巧号,春花似錦、人聲如沸姥闭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽棚品。三九已至靠欢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間铜跑,已是汗流浹背门怪。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锅纺,地道東北人掷空。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像囤锉,于是被迫代替她去往敵國和親坦弟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評論 2 351