從零開始學(xué)Python(八):Python多線程和隊(duì)列

很久沒有更新博文啦,在家過春節(jié)已經(jīng)變懶了-_-,不過答應(yīng)大家更完這個(gè)python的入門系列,偶還是會(huì)繼續(xù)努力的!另外祝愿大家新年快樂,事事順心!

線程的概念

我們學(xué)習(xí)的很多編程語言,比如java,oc等,都會(huì)有線程這個(gè)概念.線程的用途非常的廣泛,給我們開發(fā)中帶來了很多的便利.主要用于一些串行或者并行的邏輯處理,比如點(diǎn)擊某個(gè)按鈕的時(shí)候,我們可以通過進(jìn)度條來控制線程的運(yùn)行時(shí)間,以便于更好的用于用戶的交互.

每個(gè)獨(dú)立的線程都包含一個(gè)程序的運(yùn)行入口,順序的執(zhí)行序列和一個(gè)程序運(yùn)行的出口.線程必須在程序中存在,而不能獨(dú)立于程序運(yùn)行!

每個(gè)線程都有他自己的一組cpu儲(chǔ)存器,稱為線程的上下文,該上下文反應(yīng)了線程上次運(yùn)行的cpu寄存器的狀態(tài).指令指針和堆棧指針寄存器是線程上下文中兩個(gè)最重要的寄存器,線程總是在進(jìn)程得到上下文運(yùn)行,這些地址都用于標(biāo)志擁有線程的進(jìn)程地址空間中的內(nèi)存.

Python線程

在Python中,主要提供了thread和threading兩個(gè)線程模塊,thread模塊提供了最基礎(chǔ)的,最低級(jí)的線程函數(shù),和一個(gè)簡單的鎖.threading模塊是thread模塊的封裝進(jìn)階,提供了多樣的線程屬性和方法.下面我們會(huì)對(duì)該兩個(gè)模塊逐個(gè)解析.

thread模塊(不推薦使用)

thread模塊常用的函數(shù)方法:

函數(shù)名 描述
start_new_thread(function, args, kwargs=None) 產(chǎn)生一個(gè)新線程,function為線程要運(yùn)行的函數(shù)名,args是函數(shù)參數(shù)(tuple元組類型),kwargs為可選參數(shù)
allocate_lock() 分配一個(gè)locktype類型的線程鎖對(duì)象
exit() 線程退出
_count() 返回線程數(shù)量,注意不包含主線程哦,所以在主線程運(yùn)行該方法返回的是0
locked locktype 鎖,返回true為已鎖
release() 釋放locktype對(duì)象鎖
acquire() 鎖定

下面我們來舉個(gè)例子:

import thread,time


def loop1():
    print '線程個(gè)數(shù)-' + str(thread._count())
    i=0
    try:
        while i < 100:
            print i
            time.sleep(1)
            i = i + 1
    except Exception as e:
        print e


thread.start_new_thread(loop1,())

運(yùn)行上面代碼,你會(huì)發(fā)現(xiàn)loop1方法中的循環(huán)打印并沒有被調(diào)用,而是直接返回了一個(gè)異常:

Unhandled exception in thread started by 
sys.excepthook is missing
lost sys.stderr

這時(shí)你可能會(huì)一遍又一遍的檢查代碼,以為是代碼錯(cuò)了(沒錯(cuò),那個(gè)人就是我),其實(shí)我們代碼本身是沒有錯(cuò)誤的,是早期python的thread模塊一個(gè)缺陷(這個(gè)缺陷也是導(dǎo)致這個(gè)模塊被官方不推薦使用的主要原因):當(dāng)我們?cè)谥骶€程中使用start_new_thread創(chuàng)建新的線程的時(shí)候,主線程無法得知線程何時(shí)結(jié)束,他不知道要等待多久,導(dǎo)致主線程已經(jīng)執(zhí)行完了,子線程卻還未完成,于是系統(tǒng)拋出了這個(gè)異常.

解決這個(gè)異常的方法有兩種:

1.讓主線程休眠足夠長的時(shí)間來等待子線程返回結(jié)果:

import thread,time


def loop1():
    print '線程個(gè)數(shù)-' + str(thread._count())
    i=0
    try:
        while i < 100:
            print i
            time.sleep(1)
            i = i + 1
    except Exception as e:
        print e


thread.start_new_thread(loop1,())
time.sleep(1000)   #讓主線程休眠1000秒,足夠子線程完成

2.給線程加鎖(早期python線程使用一般處理)

import thread,time


def loop1(lock):
    print '線程個(gè)數(shù)-' + str(thread._count())
    i=0
    try:
        while i < 100:
            print i
            time.sleep(1)
            i = i + 1
    except Exception as e:
        lock.release()
        print e

    lock.release()    #執(zhí)行完畢,釋放鎖


lock=thread.allocate_lock()   #獲取locktype對(duì)象
lock.acquire()   #鎖定

thread.start_new_thread(loop1,(lock,))
while lock.locked():    #等待線程鎖釋放
    pass

以上就是thread模塊的常用線程用法,我們可以看出,thread模塊提供的線程操作是極其有限的,使用起來非常的不靈活,下面我們介紹他的同胞模塊threading.

threading模塊(推薦使用)

threading模塊是thread的完善,有一套成熟的線程操作方法,基本能完成我們所需的所有線程操作

threading常用方法:

  • 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é)果看尼。
  • 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)建線程有兩種方式:

1.直接通過初始化thread對(duì)象創(chuàng)建:

#coding=utf-8
import threading,time

def test():
    t = threading.currentThread()  # 獲取當(dāng)前子線程對(duì)象
    print t.getName()  # 打印當(dāng)前子線程名字

    i=0
    while i<10:
        print i
        time.sleep(1)
        i=i+1




m=threading.Thread(target=test,args=(),name='循環(huán)子線程')   #初始化一個(gè)子線程對(duì)象,target是執(zhí)行的目標(biāo)函數(shù),args是目標(biāo)函數(shù)的參數(shù),name是子線程的名字
m.start()
t=threading.currentThread()   #獲取當(dāng)前線程對(duì)象,這里其實(shí)是主線程
print t.getName()   #打印當(dāng)前線程名字,其實(shí)是主線程名字

可以看到打印結(jié)果:

循環(huán)子線程
MainThread
0
1
2
3
4
5
6
7
8

2.通過基礎(chǔ)thread類來創(chuàng)建

import threading,time
class myThread (threading.Thread):   #創(chuàng)建一個(gè)自定義線程類mythread,繼承Thread

def __init__(self,name):
    """
    重新init方法
    :param name: 線程名
    """
    super(myThread, self).__init__(name=name)
    # self.lock=lock

    print '線程名'+name

def run(self):
    """
    重新run方法,這里面寫我們的邏輯
    :return:
    """
    i=0
    while i<10:
        print i
        time.sleep(1)
        i=i+1


if __name__=='__main__':
    t=myThread('mythread')
    t.start()

輸出:

線程名線程
0
1
2
3
4
5
6
7
8
9

線程同步

如果兩個(gè)線程同時(shí)訪問同一個(gè)數(shù)據(jù)的時(shí)候,可能會(huì)出現(xiàn)無法預(yù)料的結(jié)果,這時(shí)候我們就要用到線程同步的概念.

上面我們講到thread模塊的時(shí)候,已經(jīng)使用了線程鎖的概念,thread對(duì)象的Lock和Rlock可以實(shí)現(xiàn)簡單的線程同步,這兩個(gè)對(duì)象都有acquire方法和release方法独泞,對(duì)于那些需要每次只允許一個(gè)線程操作的數(shù)據(jù),可以將其操作放到acquire和release方法之間.

下面我們來舉例說明,我們需要實(shí)現(xiàn)3個(gè)線程同時(shí)訪問一個(gè)全局變量,并且改變這個(gè)變量:

1.不加鎖的情況
import threading,time

lock=threading.Lock()   #全局的鎖對(duì)象
temp=0    #我們要多線程訪問的全局屬性

class myThread (threading.Thread):   #創(chuàng)建一個(gè)自定義線程類mythread,繼承Thread

    def __init__(self,name):
        """
        重新init方法
        :param name: 線程名
        """
        super(myThread, self).__init__(name=name)
        # self.lock=lock

        print '線程名'+name

    def run(self):
        """
        重新run方法,這里面寫我們的邏輯
        :return:
        """
        global temp,lock

        i=0
        while i<2:   #這里循環(huán)兩次累加全局變量,目的是增加出錯(cuò)的概率

            temp=temp+1  #在子線程中實(shí)現(xiàn)對(duì)全局變量加1

            print self.name+'--temp=='+str(temp)
            i=i+1

    if __name__=='__main__':


        t1=myThread('線程1')
        t2=myThread('線程2')
        t3=myThread('線程3')

        #創(chuàng)建三個(gè)線程去執(zhí)行訪問
        t1.start()
        t2.start()
        t3.start()

執(zhí)行結(jié)果(由于程序運(yùn)行很快,你多運(yùn)行幾次就可能會(huì)出現(xiàn)以下結(jié)果): 我們可以發(fā)現(xiàn),線程1和線程2同時(shí)訪問到了變量,導(dǎo)致打印出現(xiàn)對(duì)等情況

線程名線程1
線程名線程2
線程名線程3
線程1--temp==1線程2--temp==2
線程1--temp==3

線程2--temp==4
線程3--temp==5
線程3--temp==6
2.加鎖情況
import threading,time

lock=threading.Lock()   #全局的鎖對(duì)象
temp=0    #我們要多線程訪問的全局屬性

class myThread (threading.Thread):   #創(chuàng)建一個(gè)自定義線程類mythread,繼承Thread

    def __init__(self,name):
        """
        重新init方法
        :param name: 線程名
        """
        super(myThread, self).__init__(name=name)
        # self.lock=lock

        print '線程名'+name

    def run(self):
        """
        重新run方法,這里面寫我們的邏輯
        :return:
        """
        global temp,lock

        if lock.acquire():  #這里線程進(jìn)來訪問變量的時(shí)候,鎖定變量
            i = 0
            while i < 2:  # 這里循環(huán)兩次累加全局變量,目的是增加出錯(cuò)的概率

                temp = temp + 1  # 在子線程中實(shí)現(xiàn)對(duì)全局變量加1

                print self.name + '--temp==' + str(temp)
                i = i + 1

            lock.release()  #訪問完畢,釋放鎖讓另外的線程訪問



if __name__=='__main__':


    t1=myThread('線程1')
    t2=myThread('線程2')
    t3=myThread('線程3')

    #創(chuàng)建三個(gè)線程去執(zhí)行訪問
    t1.start()
    t2.start()
    t3.start()

運(yùn)行結(jié)果(不管運(yùn)行多少次,都不會(huì)出現(xiàn)同時(shí)訪問的情況):

線程名線程1
線程名線程2
線程名線程3
線程1--temp==1
線程1--temp==2
線程2--temp==3
線程2--temp==4
線程3--temp==5
線程3--temp==6

線程同步很多地方都會(huì)用到,比如搶票,抽獎(jiǎng),我們需要對(duì)一些資源進(jìn)行鎖定,以防止多線程訪問的時(shí)候出現(xiàn)不可預(yù)知的情況.

線程隊(duì)列

python中的隊(duì)列用到了Queue模塊,該模塊提供了同步的,安全的對(duì)序列,包括FIFO(先入先出)隊(duì)列Queue苔埋,LIFO(后入先出)隊(duì)列LifoQueue懦砂,和優(yōu)先級(jí)隊(duì)列PriorityQueue.這些隊(duì)列都實(shí)現(xiàn)了鎖原語,能夠在多線程中直接使用组橄≤癖欤可以使用隊(duì)列來實(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í)行別的操作

例子:
tags=['one','tow','three','four','five','six']

q=Queue.LifoQueue()   #先入先出隊(duì)列
for t in tags:
q.put(t)   #將數(shù)組數(shù)據(jù)加入隊(duì)列
for i in range(6):
    print q.get()    #取出操作可以放在不同的線程中,不會(huì)出現(xiàn)同步的問題

結(jié)果:

six
five
four
three
tow
one

Q&A

這章的多線程就到這里了,我們主要講述了他的基本用法,更多的用法我們可以在以后的開發(fā)過程中,根據(jù)自己邏輯去設(shè)計(jì).

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市费奸,隨后出現(xiàn)的幾起案子弥激,更是在濱河造成了極大的恐慌,老刑警劉巖愿阐,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件微服,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡缨历,警方通過查閱死者的電腦和手機(jī)以蕴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門糙麦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人丛肮,你說我怎么就攤上這事赡磅。” “怎么了宝与?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵焚廊,是天一觀的道長。 經(jīng)常有香客問我习劫,道長咆瘟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任诽里,我火速辦了婚禮袒餐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谤狡。我一直安慰自己灸眼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布墓懂。 她就那樣靜靜地躺著幢炸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拒贱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天佛嬉,我揣著相機(jī)與錄音逻澳,去河邊找鬼。 笑死暖呕,一個(gè)胖子當(dāng)著我的面吹牛斜做,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播湾揽,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼瓤逼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了库物?” 一聲冷哼從身側(cè)響起霸旗,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎戚揭,沒想到半個(gè)月后诱告,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡民晒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年精居,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锄禽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡靴姿,死狀恐怖沃但,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情佛吓,我是刑警寧澤宵晚,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站辈毯,受9級(jí)特大地震影響坝疼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谆沃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一钝凶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧唁影,春花似錦耕陷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至锌介,卻和暖如春嗜诀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背孔祸。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國打工隆敢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人崔慧。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓拂蝎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親惶室。 傳聞我的和親對(duì)象是個(gè)殘疾皇子温自,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

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