1. Python 多線程入門,這一篇文章就夠了

        共 6576字,需瀏覽 14分鐘

         ·

        2020-11-24 03:09

        點(diǎn)擊上方“與你一起學(xué)算法”,選擇“星標(biāo)”公眾號(hào)

        重磅干貨,第一時(shí)間送達(dá)

        Python 和多線程

        提及 Python 啊,我想你首先想到的就是「人生苦短,我用 Python」了?,F(xiàn)在 Python 的熱度可謂是非常的高,感覺程序員要是不學(xué) Python 的話,就有一種 out 了的感覺,雖然現(xiàn)在工業(yè)界使用 Python 的人數(shù)遠(yuǎn)沒有 Java 的人多, 但 Python 是未來的趨勢(shì)是非常明顯的,因此呢,學(xué)習(xí) Python 自然就是一件很有必要的事情了,今天呢,我就帶你一起聊聊 Python 多線程相關(guān)的那些事。

        關(guān)于多線程啊,我想你肯定不陌生,無論是高級(jí)語言的鼻祖 C 語言、還是 C++、Java,都支持多線程、多進(jìn)程,而且這部分知識(shí)無論是在求職面試還是在日常的工作開發(fā)中,都會(huì)涉及到,不巧的是呢,這部分知識(shí)在老師講課過程中是很少涉及的,甚至是直接不講,我記得我當(dāng)時(shí)老師就沒有講,這不是說老師不合格,偷懶了,而是一門語言涉及到的知識(shí)太多了,老師只能把一些基礎(chǔ)的東西交給你,帶你入門,剩下的就需要自己去摸索、自學(xué)了。

        線程與進(jìn)程

        既然提到多線程,多進(jìn)程了,那就有必要先了解下線程和進(jìn)程的相關(guān)概念了。要不然的話后面的內(nèi)容理解起來也是有點(diǎn)費(fèi)勁的。

        提到進(jìn)程啊,我想你肯定是不陌生的,我們?cè)陔娔X上打開一個(gè)軟件,就是開啟了一個(gè)進(jìn)程,更具體的來說,Windows 系統(tǒng)你可以通過資源管理器進(jìn)行查看當(dāng)前電腦啟動(dòng)的進(jìn)程數(shù)。

        用比較正式的話來說,進(jìn)程就是處于運(yùn)行中的程序,并且具有一定獨(dú)立的功能。進(jìn)程是操作系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位。

        然后就是線程,它是進(jìn)程的組成部分,一個(gè)線程可以包含多個(gè)線程,多個(gè)線程可以共用這個(gè)進(jìn)程的資源,相比于進(jìn)程,線程更加輕量級(jí)。

        舉個(gè)例子來說明下:我們的生活都是以家單位的,每家每戶每天都有自己的計(jì)劃安排、互不影響,這時(shí)候,每家就相當(dāng)于一個(gè)進(jìn)程,但是呢,需要受到國(guó)家的管制,比如說,買房限購(gòu)、戶口問題啊等等需要國(guó)家統(tǒng)一出臺(tái)政策進(jìn)行管理,這時(shí)候國(guó)家就相當(dāng)于操作系統(tǒng),而房子、戶口就相當(dāng)于資源。但是對(duì)于每一家來說,又有不同的人,這時(shí)候,每個(gè)人就相當(dāng)于一個(gè)線程,多個(gè)線程之間共用家里的一些資源,就是家里的人共用家里的一些東西。雖然例子不是很恰當(dāng),但對(duì)于理解線程和進(jìn)程還是有很大幫助的。

        線程的幾種狀態(tài)

        線程狀態(tài)一共有五種,包括如下:

        • 新建

        • 就緒

        • 運(yùn)行

        • 阻塞

        • 死亡

        它們之間的關(guān)系如下圖所示:

        實(shí)現(xiàn)方式

        接下來,我們就來看看如何在 Python 里面實(shí)現(xiàn)多線程。總的來說,如果你了解過其他語言實(shí)現(xiàn)多線程的方式,比如說 Java的話,那對(duì)于理解 Python 實(shí)現(xiàn)多線程是非常有幫助的。Python 實(shí)現(xiàn)多線程有兩種方式:

        1. 使用 threading 模塊的 Thread 類的構(gòu)造器創(chuàng)建線程

        2. 繼承 threading 模塊的 Thread 類創(chuàng)造線程類

        看到這,你是不是發(fā)現(xiàn)這和 Java 實(shí)現(xiàn)多線程的方式很相類,不錯(cuò),確實(shí)就是這樣,所以再次印證了那句話,只要學(xué)好了一門語言,學(xué)習(xí)其他語言都會(huì)起到事半功倍的效果。

        使用 threading 模塊的 Thread 類的構(gòu)造器創(chuàng)建線程

        我們先用第一種方法來編寫一個(gè)多線程程序

        #!/usr/bin/python
        #?-*-?coding:?utf-8?-*-
        import?threading


        #?定義一個(gè)簡(jiǎn)單的方法,用于多線程的執(zhí)行體
        def?action(number):
        ??for?i?in?range(number):
        ????#?調(diào)用?threading?模塊的?current_thread()?函數(shù)來獲取當(dāng)前線程
        ????#?調(diào)用當(dāng)前線程的?getName()?函數(shù)來獲取線程名
        ????print("{},{}".format(threading.current_thread().getName(),?i))

        number?=?5
        for?i?in?range(5):
        ??print("{},{}".format(threading.current_thread().getName(),?i))
        ??if?i?==?3:
        ????#?創(chuàng)建并啟動(dòng)第一個(gè)線程
        ????t1?=?threading.Thread(target=action,?args=(number,?))
        ????t1.start()
        ????#?創(chuàng)建并啟動(dòng)第二個(gè)線程
        ????t2?=?threading.Thread(target=action,?args=(number,?))
        ????t2.start()

        看起來是不是很簡(jiǎn)單,很我們平常寫的 Python 程序并沒有特別大的不同,但是還是有很一些情況是需要注意的,其中最重要的就是 threading.Thread(),我在這里重點(diǎn)介紹下。

        首先它是一個(gè)類,我們可以通過 type(threading.Thread) 來進(jìn)行查看,它的構(gòu)造函數(shù)如下所示:

        __init__(self, group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

        group 應(yīng)該為None,這個(gè)我們不用管,它是為了日后擴(kuò)展 ThreadGroup 類實(shí)現(xiàn)而保留的一個(gè)參數(shù)

        target 是我們需要重視的一個(gè)參數(shù), 我們想讓哪個(gè)函數(shù)并發(fā)執(zhí)行,這個(gè)函數(shù)就是 target 的參數(shù)值,注意只寫函數(shù)名,不需要寫 ()

        name 是線程名稱,默認(rèn)情況下,由"Thread-N"的格式構(gòu)成一個(gè)唯一的名稱,其中 N 是小的十進(jìn)制數(shù)

        args 是用于調(diào)用目標(biāo)函數(shù)的參數(shù)元祖, 注意是元祖, 如果你只想傳一個(gè)參數(shù)的話,也應(yīng)該這樣寫 (args1,), 而不是 (args)

        kwargs 是用于調(diào)用目標(biāo)函數(shù)的關(guān)鍵字參數(shù)字典。默認(rèn)是 {}

        daemon 用于設(shè)置該線程是否為守護(hù)模式,如果是 None, 線程默認(rèn)將繼承當(dāng)前線程的守護(hù)模式屬性。

        一般來說,我們需要注意的就是 target 參數(shù)、args 參數(shù),其他的參數(shù)用到的時(shí)候可以再查。

        另一點(diǎn)需要我們需要注意的一點(diǎn)就是啟動(dòng)線程的方法是 start 方法,可能你也知道線程也有 run 方法,這一塊也會(huì)在第二種方法中進(jìn)行介紹,但是啟動(dòng)線程的方法是 start 方法,要不然就變成了單線程程序。

        繼承 threading 模塊的 Thread 類創(chuàng)造線程類

        接下來我們來看下如何使用第二種方法實(shí)現(xiàn)多線程

        #!?/usr/bin/python
        #?-*-?coding:utf-8?-*-
        import?threading
        from?threading?import?Thread

        #?繼承?threading.Thread
        class?MyThread(Thread):
        ??def?__init__(self,?number):
        ????super().__init__()
        ????self.number?=?number
        ??#?重載?run()?方法
        ??def?run(self):
        ????for?i?in?range(self.number):
        ??????print("{},?{}".format(threading.current_thread().getName(),?i))

        number?=?5
        for?i?in?range(5):
        ??print("{},?{}".format(threading.current_thread().getName(),?i))
        ??if?i?==?3:
        ????t1?=?MyThread(number=number)
        ????t1.start()
        ????t2?=?MyThread(number=number)
        ????t2.start()

        第二種方法就是繼承 Threading.Thread 類。然后重載 run() 方法。

        其實(shí)我看來的話,感覺第二種方法更適合在項(xiàng)目中使用,因?yàn)樗幽K化,比較清晰。

        另外還有一個(gè)方法需要注意的就是 join() 方法,它的作用就是協(xié)調(diào)主線程和子線程的,調(diào)用 join() 后,當(dāng)前線程就會(huì)阻塞,或者來說,暫停運(yùn)行,執(zhí)行子線程,等子線程執(zhí)行完成后,主線程再接著運(yùn)行。

        生產(chǎn)者、消費(fèi)者模型

        提到多線程,最著名的就是生產(chǎn)者、消費(fèi)者模型了,那應(yīng)該如何實(shí)現(xiàn)呢?

        說實(shí)話,我當(dāng)初最開始學(xué)習(xí)生產(chǎn)者、消費(fèi)者模型的時(shí)候,心里是有點(diǎn)犯嘀咕的,感覺涉及到線程間的通信,太好解決。但是查閱了一些資料后,發(fā)現(xiàn)還是可以理解的。

        生產(chǎn)者、消費(fèi)者二者不屬于競(jìng)爭(zhēng)關(guān)系,更多的是一種捕食關(guān)系,生產(chǎn)者生產(chǎn)資源,消費(fèi)者進(jìn)行消費(fèi),就像圣湖中的牛吃草一樣。

        不知道這時(shí)候你有沒有想到一種數(shù)據(jù)結(jié)構(gòu),那就是隊(duì)列,隊(duì)列呢是一種操作受限的線性表,它只允許在隊(duì)尾入隊(duì),在隊(duì)頭
        出隊(duì),也就是先進(jìn)先出 (FIFO) 策略。

        生產(chǎn)者、消費(fèi)者模型,不就是生產(chǎn)者生產(chǎn)元素,放到隊(duì)尾,然后消費(fèi)者從隊(duì)頭消費(fèi)元素嘛。

        只不過有時(shí)候會(huì)出現(xiàn)特殊的情況

        • 隊(duì)列空了,消費(fèi)者還要消費(fèi)數(shù)據(jù)

        • 隊(duì)列滿了,生產(chǎn)者還要生產(chǎn)數(shù)據(jù)

        這是我們需要重點(diǎn)考慮了,解決了以上兩點(diǎn),這個(gè)模型也就實(shí)現(xiàn)了。

        接下來我們就來看看 Python 如何實(shí)現(xiàn)吧!

        #!/usr/bin/python
        #?-*-?coding:utf-8
        from?threading?import?Thread,?current_thread
        import?time
        import?random
        from?queue?import?Queue

        queue?=?Queue(5)


        class?ProducerThread(Thread):
        ????def?run(self):
        ????????name?=?current_thread().getName()
        ????????nums?=?range(100)
        ????????global?queue
        ????????while?True:
        ????????????num?=?random.choice(nums)
        ????????????queue.put(num)
        ????????????print("生產(chǎn)者?{}?生產(chǎn)了數(shù)據(jù)?{}".format(name,?num))
        ????????????t?=?random.randint(1,?3)
        ????????????time.sleep(t)
        ????????????print("生產(chǎn)者?{}?睡眠了?{}?秒".format(name,?t))


        class?ConsumerThread(Thread):
        ????def?run(self):
        ????????name?=?current_thread().getName()
        ????????global?queue
        ????????while?True:
        ????????????num?=?queue.get()
        ????????????queue.task_done()
        ????????????print("消費(fèi)者?{}?消耗了數(shù)據(jù)?{}".format(name,?num))
        ????????????t?=?random.randint(1,?5)
        ????????????time.sleep(t)
        ????????????print("消費(fèi)者?{}?睡眠了?{}?秒".format(name,?t))


        p1?=?ProducerThread(name="producer1")
        p1.start()
        c1?=?ConsumerThread(name="consumer1")
        c1.start()
        c2?=?ConsumerThread(name="consumer2")
        c2.start()

        看了上面的代碼,不知道你有沒有一種錯(cuò)覺,你不是說要考慮上面的兩種情況,但是你并沒有考慮啊。

        確實(shí),我沒有考慮,那是因?yàn)?Queue 在設(shè)計(jì)實(shí)現(xiàn)的時(shí)候已經(jīng)替我們考慮好了,我們直接使用就好了。

        具體就是 task_done() 函數(shù),它在隊(duì)列為空時(shí)會(huì)自動(dòng)阻塞當(dāng)前線程

        而隊(duì)列在滿的時(shí)候再添加元素也會(huì)阻塞當(dāng)前線程,這就實(shí)現(xiàn)了上面我們提到的那兩種情況。

        接下來呢,我再給你講解一個(gè)例子,帶你看看如何使用鎖。

        銀行取錢問題

        從銀行取錢的基本流程大致可以分為以下幾個(gè)步驟:

        1. 用戶輸入賬戶、密碼,系統(tǒng)判斷當(dāng)前的賬戶、密碼是否匹配。

        2. 用戶輸入取款金額

        3. 系統(tǒng)判斷賬戶余額是否大于取款金額

        4. 如果余額大于取款金額,則取款成功;如果余額小于取款金額,則取款失敗。

        乍一看,這就是日常生活中的取款操作啊,但是把它放到多線程并發(fā)的情況下,就可能會(huì)出現(xiàn)問題。不信的話,你可以試著寫下多線程的程序,然后再看下我的程序。

        #!/usr/bin/python
        #?-*-?coding:utf-8?-*-
        import?threading
        import?time


        class?Account:
        ????def?__init__(self,?account_no,?balance):
        ????????self.account_no?=?account_no
        ????????self._balance?=?balance
        ????????#?定義一個(gè)鎖
        ????????self.lock?=?threading.RLock()

        ????def?get_balance(self):
        ????????return?self._balance

        ????def?draw(self,?draw_amount):
        ????????#?對(duì)?RLock?對(duì)象進(jìn)行加鎖
        ????????self.lock.acquire()
        ????????try:
        ????????????if?self._balance?>=?draw_amount:
        ????????????????print(threading.current_thread().getName()?+?"取錢成功,吐出鈔票:"?+?str(draw_amount))
        ????????????????time.sleep(0.001)
        ????????????????self._balance?-=?draw_amount
        ????????????????print("\t余額為:"?+?str(self._balance))
        ????????????else:
        ????????????????print(threading.current_thread().getName()?+?"取錢失敗,余額不足!")
        ????????finally:
        ????????????#?釋放鎖
        ????????????self.lock.release()


        #?定義一個(gè)函數(shù)來模擬取錢操作
        def?draw(account,?draw_count):
        ????account.draw(draw_count)


        acct?=?Account("1234567",?1000)
        threading.Thread(name="甲",?target=draw,?args=(acct,?800)).start()
        threading.Thread(name="乙",?target=draw,?args=(acct,?800)).start()

        如果你想嘗試下不加鎖的情況下是否會(huì)出現(xiàn)問題,你可以把我的程序進(jìn)行修改,把加鎖的那部分去掉,然后嘗試運(yùn)行下。

        這里呢,不是說每次運(yùn)行都會(huì)出現(xiàn)問題,可能你運(yùn)行了十次也都沒有出現(xiàn)問題,但是呢,這個(gè)安全隱患是確確實(shí)實(shí)存在的,不容忽視。

        好了,今天的內(nèi)容就先分享到這里了,不知道你對(duì)多線程的內(nèi)容理解了多少,不理解的話也沒關(guān)系,多看幾遍,然后很重要的就是自己好好寫一遍實(shí)踐一下,這樣對(duì)于理解是有很大幫助的。如果遇到問題,也可以在我的公眾號(hào)底部找到我的微信聯(lián)系方式,聯(lián)系我。

        多線程的內(nèi)容有很多,今天只是分享了一些比較基礎(chǔ)的內(nèi)容,后面會(huì)再更新,歡迎關(guān)注我,一起加油進(jìn)步。

        歡迎關(guān)注我的公眾號(hào)“與你一起學(xué)算法”,如果喜歡,麻煩點(diǎn)一下“在看”~

        瀏覽 69
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 黄色一级操逼电影 | 成人国产精品一区二区毛片在线 | 大黄片国产做受 | 日本护士毛片视频免费观看 | 麻豆久久久9性大片 |