1. 程式人生 > >python3 多執行緒程式設計

python3 多執行緒程式設計

內容借鑑:

https://www.cnblogs.com/z-joshua/p/6409362.html
https://www.cnblogs.com/hoobey/p/6915638.html

執行緒的掛起與阻塞的:

掛起:一般是主動的,由系統或程式發出,甚至於輔存中去。(不釋放CPU,可能釋放記憶體,放在外存)

阻塞:一般是被動的,在搶佔資源中得不到資源,被動的掛起在記憶體,等待某種資源或訊號量(即有了資源)將他喚醒。(釋放CPU,不釋放記憶體)

另外,有一段話很形象:

 首先這些術語都是對於執行緒來說的。對執行緒的控制就好比你控制了一個僱工為你幹活。你對僱工的控制是通過程式設計來實現的。
 掛起執行緒的意思就是你對主動對僱工說:“你睡覺去吧,用著你的時候我主動去叫你,然後接著幹活”。
 使執行緒睡眠的意思就是你主動對僱工說:“你睡覺去吧,某時某刻過來報到,然後接著幹活”。
 執行緒阻塞的意思就是,你突然發現,你的僱工不知道在什麼時候沒經過你允許,自己睡覺呢,但是你不能怪僱工,肯定你這個僱主沒注意,本來你讓僱工掃地,結果掃帚被偷了或被鄰居家借去了,你又沒讓僱工繼續幹別的活,他就只好睡覺了。至於掃帚回來後,僱工會不會知道,會不會繼續幹活,你不用擔心,僱工一旦發現掃帚回來了,他就會自己去幹活的。因為僱工受過良好的培訓。這個培訓機構就是作業系統。
 
 掛起程序在作業系統中可以定義為暫時被淘汰出記憶體的程序,機器的資源是有限的,在資源不足的情況下,作業系統對在記憶體中的程式進行合理的安排,其中有的程序被暫時調離出記憶體,當條件允許的時候,會被作業系統再次調回記憶體,重新進入等待被執行的狀態即就緒態,系統在超過一定的時間沒有任何動作.

作業系統為什麼要引入掛起狀態?

掛起狀態涉及到中級排程,因為當記憶體中的某個程式需要大的記憶體空間來執行,但這時記憶體有沒有空餘空間了,那麼作業系統就回根據排程演算法把一些程序放到外存中去,以騰出空間給正在執行的程式的資料和程式,所以引如了掛起狀態。引起掛起狀態的原因有如下幾方面:

(1)終端使用者的請求。當終端使用者在自己的程式執行期間發現有可疑問題時,希望暫停使自己的程式靜止下來。亦即,使正在執行的程序暫停執行;若此時使用者程序正處於就緒狀態而未執行,則該程序暫不接受排程,以便使用者研究其執行情況或對程式進行修改。我們把這種靜止狀態成為“掛起狀態”。

(2)父程序的請求。有時父程序希望掛起自己的某個子程序,以便考察和修改子程序,或者協調各子程序間的活動。

(3)負荷調節的需要。當實時系統中的工作負荷較重,已可能影響到對實時任務的控制時,可由系統把一些不重要的程序掛起,以保證系統能正常執行。

(4)作業系統的需要。作業系統有時希望掛起某些程序,以便檢查執行中的資源使用情況或進行記賬。

(5)對換的需要。為了緩和記憶體緊張的情況,將記憶體中處於阻塞狀態的程序換至外存上。

下面再說下程序和執行緒的狀態:

程序:一般大家認為是三種狀態:執行、阻塞、就緒。也有分為五態的(多了建立和退出狀態)

執行緒:一般認為是四種狀態:New Thread(not alive)、Runnable Thread(alive)、Blocked Thread(alive)、Dead Thread(not alive)

多工的實現有3種方式:

多程序模式;
多執行緒模式;
多程序+多執行緒模式

關於程序和執行緒,大家總結一句話是“程序是作業系統分配資源的最小單元,執行緒是作業系統排程的最小單元”。
程序是具有一定獨立功能的程式關於某個資料集合上的一次執行活動,程序是系統進行資源分配和排程的一個獨立單位.
執行緒是程序的一個實體,是CPU排程和分派的基本單位,它是比程序更小的能獨立執行的基本單位.執行緒自己基本上不擁有系統資源,只擁有一點在執行中必不可少的資源(如程式計數器,一組暫存器和棧),但是它可與同屬一個程序的其他的執行緒共享程序所擁有的全部資源.
一個執行緒可以建立和撤銷另一個執行緒;同一個程序中的多個執行緒之間可以併發執行.

差別

程序和執行緒的主要差別在於它們是不同的作業系統資源管理方式。程序有獨立的地址空間,一個程序崩潰後,在保護模式下不會對其它程序產生影響,而執行緒只是一個程序中的不同執行路徑。執行緒有自己的堆疊和區域性變數,但執行緒之間沒有單獨的地址空間,一個執行緒死掉就等於整個程序死掉,所以多程序的程式要比多執行緒的程式健壯,但在程序切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行並且又要共享某些變數的併發操作,只能用執行緒,不能用程序。

執行緒可以分為:

核心執行緒:由作業系統核心建立和撤銷。
使用者執行緒:不需要核心支援而在使用者程式中實現的執行緒。

Python3 執行緒中使用的兩個模組為:

_thread---- 提供低階功能,現在已經甚少使用## 標題
threading(推薦使用) ----- 有_thread的功能都是還有許多高階功能,絕大部分情況下都是使用這個

_thread和threading,_thread是低階模組,threading是高階模組,對_thread進行了封裝。絕大多數情況下,我們只需要使用threading這個高階模組。

建立執行緒的三個方法:

1,建立Thread例項,,將一個函式傳給它
2,建立Thread例項,,傳一個可呼叫的例項給它
3,派生Thread的子類,,並建立子類的例項 例: class 類名(threading.Thread):

啟動執行緒

啟動一個執行緒就是把一個函式傳入並建立Thread例項,然後呼叫start()開始執行。

threading.Thread

Thread 是threading模組中最重要的類之一,可以使用它來建立執行緒。有兩種方式來建立執行緒:一種是通過繼承Thread類,重寫它的run方法;另一種是建立一個threading.Thread物件,在它的初始化函式 (init)中將可呼叫物件作為引數傳入。
  
呼叫方法引入執行緒:

     1 import threading
     2 import time
     3 '''`在這裡插入程式碼片`
     4 沒有引入類,直接建立Thread例項,呼叫threading.Thread()方法
     5 '''
     6 def loop(i,sleep_time):
     7     print('loop:',i,'start')
     8     time.sleep(sleep_time)
     9     print('loop:',i,'done')
    10 def main():
    11     sleep_time = [4,2]
    12     loops = range(sleep_time.__len__())
    13     thread_list = []
    14     for i in loops:
    15         t = threading.Thread(target=loop,args=(i,sleep_time[i]))
    16         t.start()
    17         thread_list.append(t)
    18     for i in thread_list:
    19         i.join()#主執行緒等待所有子執行緒執行完畢再繼續執行
    20     print('all thread have done!')
    21 
    22 if __name__ == "__main__":
    23     main()

呼叫Thread函式實現面向物件思想

 1 import threading
 2 import time
 3 '''
 4 引入類,實現了面向物件程式設計的思想,可擴充套件性強
 5 '''
 6 class threadFunc(object):
 7     def __init__(self,func,i,sleep_time):
 8         self.func = func
 9         self.i = i
10         self.sleep_time = sleep_time
11     def __call__(self, *args, **kwargs):#回撥函式執行self.func(*args)
12         self.func(self.i,self.sleep_time)
13 def loop(i,sleep_time):
14     print('loop:', i, 'start')
15     time.sleep(sleep_time)
16     print('loop:', i, 'done')
17 def main():
18     loops = [4,2]
19     nloop = range(loops.__len__())
20     thread_list = []
21     for i in nloop:
22         t = threading.Thread(target=threadFunc(loop,i,loops[i]))
23         t.start()
24         thread_list.append(t)
25     for i in thread_list:
26         
27         i.join()
28     print('all thread have done!')
29 
30 if __name__ == '__main__':
31     main()

通過繼承實現執行緒的建立

 1 import threading
 2 import time
 3 
 4 class Mythread(threading.Thread):
 5     def __init__(self,func,args,name = ''):
 6 
 7         '''
 8         官方文件
 9         If a subclass overrides the constructor, it must make sure to invoke
10         the base class constructor (Thread.__init__()) before doing anything
11         else to the thread.
12         如果子類重寫建構函式,則必須確保在對執行緒執行任何其他操作之前呼叫基類建構函式(Thread._init_())。
13         '''
14         threading.Thread.__init__(self)#繼承父類的__init()__方法很重要 #######
15         self.func = func
16         self.args = args
17         self.name = name
18     def run(self):
19         self.func(*self.args)
20 def loop(i,sleep_time):
21     print('loop:', i, 'start')
22     time.sleep(sleep_time)
23     print('loop:', i, 'done')
24 def main():
25     loops = [4,2]
26     thread_list = []
27     nloop = range(len(loops))
28     for i in nloop:
29         t = Mythread(loop,(i,loops[i]),loop.__name__)
30         t.start()
31         thread_list.append(t)
32     for i in thread_list:
33         i.join()
34     print('all have done!')
35 
36 
37 if __name__ == '__main__':
38     main()

Thread.join()

呼叫Thread.join將會使主調執行緒堵塞,直到被呼叫執行緒執行結束或超時。引數timeout是一個數值型別(thread.join(timeout=時間)),表示超時時間,如果未提供該引數,那麼主調執行緒將一直堵塞到被調執行緒結束。

threading.Lock與RLock

為什麼要用鎖這個控制機制?在同一個程序中的資源,執行緒是共享的,如果不進行資源的合理分配,對資料造成破壞,使得執行緒執行的結果不可預期。這種現象稱為“執行緒不安全”。所以不排除兩個程序同時訪問一個數據的情況發生,一旦發生就會造成資料修改錯誤。
  
  在threading模組中,定義兩種型別鎖:threading.Lock和threading.RLock。它們之間有一點細微的區別,通過比較下面兩段程式碼來說明:

 1 import threading
 2 lock = threading.Lock() 
 3 #Lock物件
 4 lock.acquire()
 5 lock.acquire() 
 6 #產生了死瑣。
 7 lock.release()
 8 lock.release()
 9   
10 import threading
11 rLock = threading.RLock() 
12 #RLock物件
13 rLock.acquire()
14 rLock.acquire() 
15 #在同一執行緒內,程式不會堵塞。
16 rLock.release()
17 rLock.release()

總結:Lock不允許在同一執行緒中多次被acquire(),RLock可以。但是RLock釋放的時候也必須釋放對應的之前的acquire次數。比如你之前acuire()了5次後面就必須要有5個release()才算真正釋放RLock。

threading.Condition

可以把Condiftion理解為一把高階的鎖,它提供了比Lock, RLock更高階的功能,允許我們能夠控制複雜的執行緒同步問題。

threadiong.Condition在內部維護一個鎖物件(預設是RLock),可以在建立Condigtion物件的時候把鎖物件作為引數傳入。

condition方法:

1.acquire():執行緒獲得鎖

2.release():釋放鎖

3.wait():執行緒掛起狀態,會自動釋放鎖。

4.notify():通知(喚醒)其他阻塞的一個執行緒(如果有多個執行緒等待這個鎖也只喚醒一個)獲得鎖,並不會釋放鎖

5.notifyALL():通知(喚醒)其他阻塞的執行緒獲得鎖,並不會釋放鎖

例子(捉迷藏)

#encoding:utf-8

import threading,time

class Seeker(threading.Thread):
    def __init__(self,cond,name):
        threading.Thread.__init__(self) ###
        self.cond=cond
        self.name=name

def run(self):
    self.cond.acquire()
    print ('1:我把眼睛蒙上了')
    self.cond.notify()
    self.cond.wait()


    print ('3:我找到你啦')
    self.cond.notify()
    self.cond.wait()


    print ('5:我贏了')
    self.cond.release()


class Hider(threading.Thread):
    def __init__(self,cond,name):
        threading.Thread.__init__(self) ####
        self.cond=cond
        self.name=name

def run(self):
    time.sleep(1)
    self.cond.acquire()
    print ('2:我已經藏好了')
    self.cond.notify()
    self.cond.wait()

    print('4:被你找到啦')
    self.cond.notify() 
    self.cond.release()



cond=threading.Condition()
seeker=Seeker(cond,'seeker')
hider=Hider(cond,'hider')
seeker.start()
hider.start()

執行結果:
1:我把眼睛蒙上了
2:我已經藏好了
3:我找到你啦
4:被你找到啦
5:我贏了

threading.Event

在初始情況下,event 物件中的訊號標誌被設定為假。如果有執行緒等待一個 event 物件,而這個 event 物件的標誌為假,那麼這個執行緒將會被一直阻塞直至該標誌為

Event實現與Condition類似的功能,不過比Condition簡單一點。它通過維護內部的識別符號來實現執行緒間的同步問題。

Event.wait() : 堵塞執行緒,直到Event物件內部標識位被設為True或超時(如果提供了引數timeout)。

Event.clear() : 將標誌位置於false狀態。

Event.set() : 設定標誌位為true

Event.isSet() : 判斷標誌位狀態

event 物件的一個重要特點是當它被設定為真時會喚醒所有等待它的執行緒。如果你只想喚醒單個執行緒, 最好是使用訊號量或者 Condition物件來替代。 event 物件最好單次使用,就是說,你建立一個 event 物件,讓某個執行緒等待這個物件,一旦這個物件被設定為真,你就 應該丟棄它。儘管可以通過 clear() 方法來重 置 event 物件,但是很難確保安全地清理 event 物件並對它重新賦值。很可能會發生錯 過事件、死鎖或者其他問題(特別是,你無法保證重置 event 物件的程式碼會線上程再次等待這個 event 物件之前執行)。如果一個執行緒需要不停地重複使用 event 物件,你 最好使用 Condition 物件來代替。

例子紅綠燈:

import threading
import time
def car(event):
    while True:
        if event.isSet():
            print ('綠燈或者黃燈亮,可以通行。')
            time.sleep(2)
        else:
            print ('紅燈亮,禁止通行。')
            time.sleep(2)
        


def light(event):
    while True:
        event.clear()
        print('紅燈亮')
        time.sleep(6)
        event.set()
        print('綠燈亮')
        time.sleep(4)
        print('黃燈亮')
        time.sleep(2)

def main():
    event=threading.Event()
    c=threading.Thread(target=car,args=(event,))
    l=threading.Thread(target=light,args=(event,))
    l.start()
    c.start()

if __name__=='__main__':    #要知道這個是什麼意思,類似的還有__repr__,__init__等等
    main()

執行結果:

紅燈亮
紅燈亮,禁止通行。
紅燈亮,禁止通行。
紅燈亮,禁止通行。
綠燈亮
綠燈或者黃燈亮,可以通行。
綠燈或者黃燈亮,可以通行。
黃燈亮
綠燈或者黃燈亮,可以通行。

threading.BoundedSemaphore訊號量(PV操作)

訊號量是最古老的同步原語之一。它是一個計數器,當資源消耗時遞減,當資源釋放時遞增。你可以認為訊號量代表它們的資源可用或不可用。消耗資源使計數器遞減的操作習慣上稱為P() (來源於荷蘭單詞probeer/proberen),也稱為wait、try、acquire、pend或procure。相對地,當一個執行緒對一個資源完成操作時,該資源需要返回資源池中。這個操作一般稱為 V()(來源於荷蘭單詞 verhogen/verhoog),也稱為 signal、increment、release、post、vacate。Python 簡化了所有的命名,使用和鎖的函式/方法一樣的名字:acquire 和 release。訊號量比鎖更加靈活,因為可以有多個執行緒,每個執行緒擁有有限資源的一個例項。