Python3 多線程

多線程類似于同時(shí)執(zhí)行多個(gè)不同程序斯入,多線程運(yùn)行有如下優(yōu)點(diǎn):

  • 使用線程可以把占據(jù)長(zhǎng)時(shí)間的程序中的任務(wù)放到后臺(tái)去處理盾致。
  • 用戶界面可以更加吸引人痹愚,這樣比如用戶點(diǎn)擊了一個(gè)按鈕去觸發(fā)某些事件的處理岩齿,可以彈出一個(gè)進(jìn)度條來(lái)顯示處理的進(jìn)度
  • 程序的運(yùn)行速度可能加快
  • 在一些等待的任務(wù)實(shí)現(xiàn)上如用戶輸入呛哟、文件讀寫和網(wǎng)絡(luò)收發(fā)數(shù)據(jù)等,線程就比較有用了胸梆。在這種情況下我們可以釋放一些珍貴的資源如內(nèi)存占用等等敦捧。

線程在執(zhí)行過(guò)程中與進(jìn)程還是有區(qū)別的须板。每個(gè)獨(dú)立的線程有一個(gè)程序運(yùn)行的入口、順序執(zhí)行序列和程序的出口兢卵。但是線程不能夠獨(dú)立執(zhí)行习瑰,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個(gè)線程執(zhí)行控制秽荤。
每個(gè)線程都有他自己的一組CPU寄存器甜奄,稱為線程的上下文,該上下文反映了線程上次運(yùn)行該線程的CPU寄存器的狀態(tài)窃款。
指令指針和堆棧指針寄存器是線程上下文中兩個(gè)最重要的寄存器课兄,線程總是在進(jìn)程得到上下文中運(yùn)行的,這些地址都用于標(biāo)志擁有線程的進(jìn)程地址空間中的內(nèi)存晨继。

  • 線程可以被搶占(中斷)烟阐。
  • 在其他線程正在運(yùn)行時(shí)紊扬,線程可以暫時(shí)擱置(也稱為睡眠) -- 這就是線程的退讓。

線程可以分為:

  • 內(nèi)核線程:由操作系統(tǒng)內(nèi)核創(chuàng)建和撤銷扩淀。
  • 用戶線程:不需要內(nèi)核支持而在用戶程序中實(shí)現(xiàn)的線程。

Python3 線程中常用的兩個(gè)模塊為:

  • _thread
  • threading(推薦使用)

thread 模塊已被廢棄啤挎。用戶可以使用 threading 模塊代替驻谆。所以庆聘,在 Python3 中不能再使用"thread" 模塊。為了兼容性伙判,Python3 將 thread 重命名為 "_thread"象对。

開(kāi)始學(xué)習(xí)Python線程

Python中使用線程有兩種方式:函數(shù)或者用類來(lái)包裝線程對(duì)象。
函數(shù)式:調(diào)用_thread模塊中的start_new_thread()函數(shù)來(lái)產(chǎn)生新線程宴抚。語(yǔ)法如下:

_thread.start_new_thread ( function, args[, kwargs] )

參數(shù)說(shuō)明:

function - 線程函數(shù)。
args - 傳遞給線程函數(shù)的參數(shù),他必須是個(gè)tuple類型冠绢。
kwargs - 可選參數(shù)常潮。

#!/usr/bin/python3

import _thread
import time

# 為線程定義一個(gè)函數(shù)
def print_time( threadName, delay):
   count = 0
   while count < 5:
      time.sleep(delay)
      count += 1
      print ("%s: %s" % ( threadName, time.ctime(time.time()) ))

# 創(chuàng)建兩個(gè)線程
try:
   _thread.start_new_thread( print_time, ("Thread-1", 2, ) )
   _thread.start_new_thread( print_time, ("Thread-2", 4, ) )
except:
   print ("Error: 無(wú)法啟動(dòng)線程")

while 1:
   pass

執(zhí)行以上程序輸出結(jié)果如下:

Thread-1:Tue Apr 10 11:17:20 2018
Thread-2:Tue Apr 10 11:17:22 2018
Thread-1:Tue Apr 10 11:17:22 2018
Thread-1:Tue Apr 10 11:17:24 2018
Thread-2:Tue Apr 10 11:17:26 2018
Thread-1:Tue Apr 10 11:17:26 2018
Thread-1:Tue Apr 10 11:17:28 2018
Thread-2:Tue Apr 10 11:17:30 2018
Thread-2:Tue Apr 10 11:17:34 2018
Thread-2:Tue Apr 10 11:17:38 2018

線程的結(jié)束一般依靠線程函數(shù)的自然結(jié)束;也可以在線程函數(shù)中調(diào)用thread.exit()孵户,他拋出SystemExit exception,達(dá)到退出線程的目的检柬。
執(zhí)行以上程后可以按下 ctrl-c to 退出竖配。

線程模塊

Python3 通過(guò)兩個(gè)標(biāo)準(zhǔn)庫(kù) _thread 和 threading 提供對(duì)線程的支持。
_thread 提供了低級(jí)別的、原始的線程以及一個(gè)簡(jiǎn)單的鎖运悲,它相比于 threading 模塊的功能還是比較有限的。
threading 模塊除了包含 _thread 模塊中的所有方法外希停,還提供的其他方法:

  • threading.currentThread(): 返回當(dāng)前的線程變量署隘。
  • threading.enumerate(): 返回一個(gè)包含正在運(yùn)行的線程的list磁餐。正在運(yùn)行指線程啟動(dòng)后、結(jié)束前诊霹,不包括啟動(dòng)前和終止后的線程。
  • threading.activeCount(): 返回正在運(yùn)行的線程數(shù)量伴箩,與len(threading.enumerate())有相同的結(jié)果鄙漏。

除了使用方法外,線程模塊同樣提供了Thread類來(lái)處理線程巩步,Thread類提供了以下方法:

  • run(): 用以表示線程活動(dòng)的方法桦踊。
  • start():啟動(dòng)線程活動(dòng)。
  • join([time]): 等待至線程中止鳄橘。這阻塞調(diào)用線程直至線程的join() 方法被調(diào)用中止-正常退出或者拋出未處理的異常-或者是可選的超時(shí)發(fā)生。
  • isAlive(): 返回線程是否活動(dòng)的术徊。
  • getName(): 返回線程名鲸湃。
  • setName(): 設(shè)置線程名。

使用threading模塊創(chuàng)建線程

我們可以通過(guò)直接從 threading.Thread 繼承創(chuàng)建一個(gè)新的子類笋除,并實(shí)例化后調(diào)用 start() 方法啟動(dòng)新線程炸裆,即它調(diào)用了線程的 run() 方法:

#!/usr/bin/python3

import threading
import time

exitFlag = 0

class myThread (threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter
    def run(self):
        print ("開(kāi)始線程:" + self.name)
        print_time(self.name, self.counter, 5)
        print ("退出線程:" + self.name)

def print_time(threadName, delay, counter):
    while counter:
        if exitFlag:
            threadName.exit()
        time.sleep(delay)
        print ("%s: %s" % (threadName, time.ctime(time.time())))
        counter -= 1

# 創(chuàng)建新線程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# 開(kāi)啟新線程
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print ("退出主線程")

以上程序執(zhí)行結(jié)果如下烹看;

開(kāi)始線程:Thread-1
開(kāi)始線程:Thread-2
Thread-1: Wed Apr 11 05:27:38 2018
Thread-2: Wed Apr 11 05:27:39 2018
Thread-1: Wed Apr 11 05:27:39 2018
Thread-1: Wed Apr 11 05:27:40 2018
Thread-2: Wed Apr 11 05:27:41 2018
Thread-1: Wed Apr 11 05:27:41 2018
Thread-1: Wed Apr 11 05:27:42 2018
退出線程:Thread-1
Thread-2: Wed Apr 11 05:27:43 2018
Thread-2: Wed Apr 11 05:27:45 2018
Thread-2: Wed Apr 11 05:27:47 2018
退出線程:Thread-2
退出主線程

線程同步

如果多個(gè)線程共同對(duì)某個(gè)數(shù)據(jù)修改惯殊,則可能出現(xiàn)不可預(yù)料的結(jié)果,為了保證數(shù)據(jù)的正確性土思,需要對(duì)多個(gè)線程進(jìn)行同步己儒。
使用Thread對(duì)象的Lock和Rlock可以實(shí)現(xiàn)簡(jiǎn)單的線程同步,這兩個(gè)對(duì)象都有acquire方法和release方法址愿,對(duì)于那些需要每次只允許一個(gè)線程操作的數(shù)據(jù)响谓,可以將其操作放到acquire和release方法之間。如下:
多線程的優(yōu)勢(shì)在于可以同時(shí)運(yùn)行多個(gè)任務(wù)(至少感覺(jué)起來(lái)是這樣)娘纷。但是當(dāng)線程需要共享數(shù)據(jù)時(shí)赖晶,可能存在數(shù)據(jù)不同步的問(wèn)題辐烂。
考慮這樣一種情況:一個(gè)列表里所有元素都是0捂贿,線程"set"從后向前把所有元素改成1,而線程"print"負(fù)責(zé)從前往后讀取列表并打印扣草。
那么颜屠,可能線程"set"開(kāi)始改的時(shí)候,線程"print"便來(lái)打印列表了密浑,輸出就成了一半0一半1粗井,這就是數(shù)據(jù)的不同步。為了避免這種情況呆瞻,引入了鎖的概念径玖。
鎖有兩種狀態(tài)——鎖定和未鎖定颤介。每當(dāng)一個(gè)線程比如"set"要訪問(wèn)共享數(shù)據(jù)時(shí)滚朵,必須先獲得鎖定;如果已經(jīng)有別的線程比如"print"獲得鎖定了辕近,那么就讓線程"set"暫停移宅,也就是同步阻塞;等到線程"print"訪問(wèn)完畢漏峰,釋放鎖以后浅乔,再讓線程"set"繼續(xù)。
經(jīng)過(guò)這樣的處理席噩,打印列表時(shí)要么全部輸出0,要么全部輸出1班挖,不會(huì)再出現(xiàn)一半0一半1的尷尬場(chǎng)面萧芙。
實(shí)例:

#!/usr/bin/python3

import threading
import time

class myThread (threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter
    def run(self):
        print ("開(kāi)啟線程: " + self.name)
        # 獲取鎖,用于線程同步
        threadLock.acquire()
        print_time(self.name, self.counter, 3)
        # 釋放鎖动羽,開(kāi)啟下一個(gè)線程
        threadLock.release()

def print_time(threadName, delay, counter):
    while counter:
        time.sleep(delay)
        print ("%s: %s" % (threadName, time.ctime(time.time())))
        counter -= 1

threadLock = threading.Lock()
threads = []

# 創(chuàng)建新線程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# 開(kāi)啟新線程
thread1.start()
thread2.start()

# 添加線程到線程列表
threads.append(thread1)
threads.append(thread2)

# 等待所有線程完成
for t in threads:
    t.join()
print ("退出主線程")

以上程序執(zhí)行結(jié)果:

開(kāi)始線程: Thread-1
開(kāi)始線程: Thread-2
Thread-1:Tue Apr 10 13:33:23 2018
Thread-1:Tue Apr 10 13:33:24 2018
Thread-1:Tue Apr 10 13:33:25 2018
Thread-2:Tue Apr 10 13:33:27 2018
Thread-2:Tue Apr 10 13:33:29 2018
Thread-2:Tue Apr 10 13:33:31 2018
退出主線程

線程優(yōu)先級(jí)隊(duì)列( Queue)

Python的Queue模塊中提供了同步的渔期、線程安全的隊(duì)列類疯趟,包括FIFO(先入先出)隊(duì)列Queue,LIFO(后入先出)隊(duì)列LifoQueue倦青,和優(yōu)先級(jí)隊(duì)列PriorityQueue盹舞。這些隊(duì)列都實(shí)現(xiàn)了鎖原語(yǔ),能夠在多線程中直接使用踢步』裼。可以使用隊(duì)列來(lái)實(shí)現(xiàn)線程間的同步。
Queue模塊中的常用方法:

  • Queue.qsize() 返回隊(duì)列的大小
  • Queue.empty() 如果隊(duì)列為空玻孟,返回True,反之False
  • Queue.full() 如果隊(duì)列滿了地粪,返回True,反之False
  • Queue.full 與 maxsize 大小對(duì)應(yīng)
  • Queue.get([block[, timeout]])獲取隊(duì)列,timeout等待時(shí)間
  • Queue.get_nowait() 相當(dāng)Queue.get(False)
  • Queue.put(item) 寫入隊(duì)列玩敏,timeout等待時(shí)間
  • Queue.put_nowait(item) 相當(dāng)Queue.put(item, False)
  • Queue.task_done() 在完成一項(xiàng)工作之后,-
  • Queue.task_done()函數(shù)向任務(wù)已經(jīng)完成的隊(duì)列發(fā)送一個(gè)信號(hào)
  • Queue.join() 實(shí)際上意味著等到隊(duì)列為空织阳,再執(zhí)行別的操作
#!/usr/bin/python3

import queue
import threading
import time

exitFlag = 0

class myThread (threading.Thread):
    def __init__(self, threadID, name, q):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.q = q
    def run(self):
        print ("開(kāi)啟線程:" + self.name)
        process_data(self.name, self.q)
        print ("退出線程:" + self.name)

def process_data(threadName, q):
    while not exitFlag:
        queueLock.acquire()
        if not workQueue.empty():
            data = q.get()
            queueLock.release()
            print ("%s processing %s" % (threadName, data))
        else:
            queueLock.release()
        time.sleep(1)

threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = queue.Queue(10)
threads = []
threadID = 1

# 創(chuàng)建新線程
for tName in threadList:
    thread = myThread(threadID, tName, workQueue)
    thread.start()
    threads.append(thread)
    threadID += 1

# 填充隊(duì)列
queueLock.acquire()
for word in nameList:
    workQueue.put(word)
queueLock.release()

# 等待隊(duì)列清空
while not workQueue.empty():
    pass

# 通知線程是時(shí)候退出
exitFlag = 1

# 等待所有線程完成
for t in threads:
    t.join()
print ("退出主線程")

以上程序執(zhí)行結(jié)果:

開(kāi)啟線程:Thread-1
開(kāi)啟線程:Thread-2
開(kāi)啟線程:Thread-3
Thread-3 processing One
Thread-1 processing Two
Thread-2 processing Three
Thread-3 processing Four
Thread-1 processing Five
退出線程:Thread-1
退出線程:Thread-2
退出線程:Thread-3
退出主線程
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市碱璃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肛真,老刑警劉巖爽航,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讥珍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡趟卸,警方通過(guò)查閱死者的電腦和手機(jī)纲酗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門觅赊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)琼稻,“玉大人,你說(shuō)我怎么就攤上這事鸠补∴值В” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵泉蝌,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我贪磺,道長(zhǎng)诅愚,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任刹前,我火速辦了婚禮雌桑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘轧飞。我一直安慰自己撒踪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布掸绞。 她就那樣靜靜地躺著衔掸,像睡著了一般俺抽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上振愿,一...
    開(kāi)封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天弛饭,我揣著相機(jī)與錄音,去河邊找鬼档桃。 笑死憔晒,一個(gè)胖子當(dāng)著我的面吹牛销凑,可吹牛的內(nèi)容都是我干的仅炊。 我是一名探鬼主播抚垄,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼桐经!你這毒婦竟也來(lái)了浙滤?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤畔咧,失蹤者是張志新(化名)和其女友劉穎誓沸,沒(méi)想到半個(gè)月后壹粟,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡洪添,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年干奢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了坐儿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宋光。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡罪佳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出赘艳,到底是詐尸還是另有隱情克握,我是刑警寧澤枷踏,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布旭蠕,位于F島的核電站,受9級(jí)特大地震影響佑稠,放射性物質(zhì)發(fā)生泄漏旗芬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一幔嫂、第九天 我趴在偏房一處隱蔽的房頂上張望誊薄。 院中可真熱鬧,春花似錦似袁、人聲如沸咐刨。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)联予。三九已至沸久,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間卷胯,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工挺峡, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人尤仍。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓吓著,卻偏偏與公主長(zhǎng)得像送挑,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子纺裁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

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