python多線程實現多任務

來源:互聯網
上載者:User

標籤:elf   多進程   實現   積累   enumerate   任務   弊端   sel   run   

1.什麼是線程?

進程是作業系統分配程式執行資源的單位,而線程是進程的一個實體,是CPU調度和分配的單位。一個進程肯定有一個主線程,我們可以在一個進程裡建立多個線程來實現多任務。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

2.一個程式實現多任務的方法

如所示,實現多任務,我們可以用幾種方法。

(1)在主進程裡面開啟多個子進程,主進程和多個子進程一起處理任務。

         有關多個進程實現多任務,可以參考我的博文:https://www.cnblogs.com/chichung/p/9532962.html

(2)在主進程裡開啟多個子線程,主線程和多個子線程一起處理任務。

(3)在主進程裡開啟多個協程,多個協程一起處理任務。

         有關多個協程實現多任務,可以參考我的博文:https://www.cnblogs.com/chichung/p/9544566.html

注意:因為用多個線程一起處理任務,會產生安全執行緒問題,所以在開發中一般使用多進程+多協程來實現多任務。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

3.多線程的建立方式

import threadingp1 = threading.Thread(target=[函數名],args=([要傳入函數的參數]))p1.start()  # 啟動p1線程

我們來類比一下多線程實現多任務。

假如你在用網易雲音樂一邊聽歌一邊下載。網易雲音樂就是一個進程。假設網易雲音樂內部程式是用多線程來實現多任務的,網易雲音樂開兩個子線程。一個用來緩衝音樂,用於現在的播放。一個用來下載使用者要下載的音樂的。這時候的代碼架構是這樣的:

import threadingimport timedef listen_music(name):    while True:        time.sleep(1)        print(name,"現正播放音樂")def download_music(name):    while True:        time.sleep(2)        print(name,"正在下載音樂")if __name__ == ‘__main__‘:    p1 = threading.Thread(target=listen_music,args=("網易雲音樂",))    p2 = threading.Thread(target=download_music,args=("網易雲音樂",))    p1.start()    p2.start()輸出:網易雲音樂 現正播放音樂網易雲音樂 正在下載音樂網易雲音樂 現正播放音樂網易雲音樂 現正播放音樂網易雲音樂 正在下載音樂網易雲音樂 現正播放音樂網易雲音樂 現正播放音樂網易雲音樂 正在下載音樂網易雲音樂 現正播放音樂網易雲音樂 現正播放音樂網易雲音樂 現正播放音樂............

觀察上面的輸出代碼可以知道:

1.CPU是按照時間片輪詢的方式來執行子線程的。cpu內部會合理分配時間片。時間片到a程式的時候,a程式如果在休眠,就會自動切換到b程式。

2.嚴謹來說,CPU在某個時間點,只在執行一個任務,但是由於CPU運行速度和切換速度快,因為看起來像多個任務在一起執行而已。

除了上面的方法建立線程,還有另一種方法。可以編寫一個類,繼承threaing.Thread類,然後重寫父類的run方法。

import threadingimport timeclass MyThread(threading.Thread):    def run(self):        for i in range(5):            time.sleep(1)            print(self.name,i)t1 = MyThread()t2 = MyThread()t3 = MyThread()t1.start()t2.start()t3.start()輸出:Thread-1 0Thread-3 0Thread-2 0Thread-1 1Thread-2 1Thread-3 1Thread-1 2Thread-3 2Thread-2 2Thread-1 3Thread-2 3Thread-3 3Thread-1 4Thread-2 4Thread-3 4

運行時無序的,說明已經啟用了多任務。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

4.線程何時開啟,何時結束

(1)子線程何時開啟,何時運行   當調用thread.start()時 開啟線程,再運行線程的代碼(2)子線程何時結束   子線程把target指向的函數中的語句執行完畢後,或者線程中的run函數代碼執行完畢後,立即結束當前子線程(3)查看當前線程數量   通過threading.enumerate()可枚舉當前啟動並執行所有線程(4)主線程何時結束   所有子線程執行完畢後,主線程才結束
import threadingimport timedef run():    for i in range(5):        time.sleep(1)        print(i)t1 = threading.Thread(target=run)t1.start()print("我會在哪裡出現")輸出:我會在哪裡出現01234

為什麼主進程(主線程)的代碼會先出現呢?因為CPU採用時間片輪詢的方式,如果輪詢到子線程,發現他要休眠1s,他會先去運行主線程。所以說CPU的時間片輪詢方式可以保證CPU的最佳運行。

那如果我想主進程輸出的那句話運行在結尾呢?該怎麼辦呢?這時候就需要用到 join() 方法了。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

5.線程的 join() 方法

import threadingimport timedef run():    for i in range(5):        time.sleep(1)        print(i)t1 = threading.Thread(target=run)t1.start()t1.join()  print("我會在哪裡出現")輸出:01234我會在哪裡出現

join() 方法可以阻塞主進程(注意只能阻塞主進程,其他子進程是不能阻塞的),直到 t1 子線程執行完,再解阻塞。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

6.線程可以共用全域變數

這個稍微實驗下就可以知道了,所以這裡不廢話。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

7.多線程共用全域變數出現的問題

我們開兩個子線程,全域變數是0,我們每個線程對他自加1,每個線程加一百萬次,這時候就會出現問題了,來,看代碼:

 1 import threading 2 import time 3  4 num = 0 5  6 def work1(loop): 7     global num 8     for i in range(loop): 9         # 等價於 num += 110         temp = num11         num = temp + 112     print(num)13 14 15 def work2(loop):16     global num17     for i in range(loop):18         # 等價於 num += 119         temp = num20         num = temp + 121     print(num)22 23 24 if __name__ == ‘__main__‘:25     t1 = threading.Thread(target=work1,args=(1000000,))26     t2 = threading.Thread(target=work2, args=(1000000,))27     t1.start()28     t2.start()29 30     while len(threading.enumerate()) != 1:31         time.sleep(1)32     print(num)33 34 輸出:35 1459526  # 第一個子線程結束後全域變數一共加到這個數36 1588806  # 第二個子線程結束後全域變數一共加到這個數37 1588806  # 兩個線程都結束後,全域變數一共加到這個數

 

奇怪了,我不是每個線程都自加一百萬次嗎?照理來說,應該最後的結果是200萬才對的呀。問題出在哪裡呢?

我們知道CPU是採用時間片輪詢的方式進行幾個線程的執行。

假設我CPU先輪詢到work1(),num此時為100,在我運行到第10行時,時間結束了!此時,賦值了,但是還沒有自加!即temp=100,num=100。

然後,時間片輪詢到了work2(),進行賦值自加。num=101了。

又回到work1()的斷點處,num=temp+1,temp=100,所以num=101。

就這樣!num少了一次自加!

在次數多了之後,這樣的錯誤積累在一起,結果只得到158806!

這就是安全執行緒問題

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

8.GIL鎖(互斥鎖)可以彌補部分安全執行緒問題。注意是部分!至於GIL鎖的弊端請關照我的這一篇博文

 

當多個線程幾乎同時修改某一個共用資料的時候,需要進行同步控制

 

線程同步能夠保證多個安全執行緒訪問競爭資源,最簡單的同步機制是引入互斥鎖。

 

互斥鎖為資源引入一個狀態:鎖定/非鎖定

 

某個線程要更改共用資料時,先將其鎖定,此時資源的狀態為“鎖定”,其他線程不能更改;直到該線程釋放資源,將資源的狀態變成“非鎖定”,其他的線程才能再次鎖定該資源。互斥鎖保證了每次只有一個線程進行寫入操作,從而保證了多線程情況下資料的正確性。

GIL鎖有三個常用步驟

 

lock = threading.Lock()  # 取得鎖lock.acquire()  # 上鎖lock.release()  # 解鎖

 

下面讓我們用GIL鎖來解決上面例子的安全執行緒問題。

import threadingimport timenum = 0lock = threading.Lock()  # 取得鎖def work1(loop):    global num    for i in range(loop):        # 等價於 num += 1        lock.acquire()  # 上鎖        temp = num        num = temp + 1        lock.release()  # 解鎖    print(num)def work2(loop):    global num    for i in range(loop):        # 等價於 num += 1        lock.acquire()  # 上鎖        temp = num        num = temp + 1        lock.release()  # 解鎖    print(num)if __name__ == ‘__main__‘:    t1 = threading.Thread(target=work1,args=(1000000,))    t2 = threading.Thread(target=work2, args=(1000000,))    t1.start()    t2.start()    while len(threading.enumerate()) != 1:        time.sleep(1)    print(num)輸出:1945267  # 第一個子線程結束後全域變數一共加到這個數2000000  # 第二個子線程結束後全域變數一共加到這個數2000000  # 兩個線程都結束後,全域變數一共加到這個數

 

python多線程實現多任務

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.