python多執行緒、鎖、event事件機制的簡單使用

君惜發表於2019-02-16

執行緒和程式

1、執行緒共享建立它的程式的地址空間,程式有自己的地址空間

2、執行緒可以訪問程式所有的資料,執行緒可以相互訪問

3、執行緒之間的資料是獨立的

4、子程式複製執行緒的資料

5、子程式啟動後是獨立的 ,父程式只能殺掉子程式,而不能進行資料交換

6、修改執行緒中的資料,都是會影響其他的執行緒,而對於程式的更改,不會影響子程式

threading.Thread

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

先來看看通過繼承threading.Thread類來建立執行緒的例子:

import threading
import time

class MyThread(threading.Thread):
    def __init__(self, arg):
        # super(MyThread, self).__init__() # 新式類繼承原有方法寫法
        threading.Thread.__init__(self)
        self.arg = arg

    def run(self):
        time.sleep(2)
        print(self.arg)

for i in range(10):
    thread = MyThread(i)
    print(thread.name)
    thread.start()

另外一種建立執行緒的方法:

import threading
import time

def process(arg):
    time.sleep(2)
    print(arg)

for i in range(10):
    t = threading.Thread(target=process, args=(i,))
    print(t.name)
    t.start()

Thread類還定義了以下常用方法與屬性:

Thread.getName() 獲取執行緒名稱
Thread.setName() 設定執行緒名稱
Thread.name 執行緒名稱

Thread.ident 獲取執行緒的識別符號。執行緒識別符號是一個非零整數,只有在呼叫了start()方法之後該屬性才有效,否則它只返回None

判斷執行緒是否是啟用的(alive)。從呼叫start()方法啟動執行緒,到run()方法執行完畢或遇到未處理異常而中斷 這段時間內,執行緒是啟用的

Thread.is_alive()
Thread.isAlive()

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

Python GIL(Global Interpreter Lock)

GIL並不是Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念。就好比C++是一套語言(語法)標準,但是可以用不同的編譯器來編譯成可執行程式碼。有名的編譯器例如GCC,INTEL C++,Visual C++等。Python也一樣,同樣一段程式碼可以通過CPython,PyPy,Psyco等不同的Python執行環境來執行。像其中的JPython就沒有GIL。然而因為CPython是大部分環境下預設的Python執行環境。所以在很多人的概念裡CPython就是Python,也就想當然的把GIL歸結為Python語言的缺陷。所以這裡要先明確一點:GIL並不是Python的特性,Python完全可以不依賴於GIL。

執行緒鎖的使用:

# 鎖:GIL 全域性直譯器 它是為了保證執行緒在執行過程中不被搶佔
number = 0
lock = threading.RLock()    # 建立鎖


def run(num):
    lock.acquire()  # 加鎖
    global number
    number += 1
    print(number)
    time.sleep(2)
    lock.release()  # 釋放鎖

for i in range(10):
    t = threading.Thread(target=run, args=(i, ))
    t.start()

Join & Daemon

主執行緒A中,建立了子執行緒B,並且在主執行緒A中呼叫了B.setDaemon(),這個的意思是,把主執行緒A設定為守護執行緒,這時候,要是主執行緒A執行結束了,就不管子執行緒B是否完成,一併和主執行緒A退出.這就是setDaemon方法的含義,這基本和join是相反的。此外,還有個要特別注意的:必須在start() 方法呼叫之前設定,如果不設定為守護執行緒,程式會被無限掛起。

class MyThread1(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        print("thread start")
        time.sleep(3)
        print(`thread end`)

print(`main start`)
thread1 = MyThread1()
# thread1.setDaemon(True)     # 設定子執行緒是否跟隨主執行緒一起結束
thread1.start()
time.sleep(1)
print(`satrt join`)
# thread1.join()    # 使主執行緒阻塞,直至子執行緒執行完畢再繼續主執行緒
print(`end join`)
def run(n):
    print(`[%s]------running----
` % n)
    time.sleep(2)
    print(`--done--`)


def main():
    for i in range(5):
        t = threading.Thread(target=run, args=[i,])
        t.start()
        # t.join()
        print(`starting thread`, t.getName())


m = threading.Thread(target=main,args=[])
# m.setDaemon(True)  # 將主執行緒設定為Daemon執行緒,它退出時,其它子執行緒會同時退出,不管是否執行完任務
m.start()
# m.join()    # 使主執行緒阻塞,直至子執行緒執行完畢再繼續主執行緒
print("---main thread done----")

執行緒鎖(互斥鎖Mutex)

一個程式下可以啟動多個執行緒,多個執行緒共享父程式的記憶體空間,也就意味著每個執行緒可以訪問同一份資料,此時,如果2個執行緒同時要修改同一份資料,會出現什麼狀況?

num = 100  # 設定一個共享變數

def subNum():
    global num # 在每個執行緒中都獲取這個全域性變數
    print(`--get num:`, num)
    time.sleep(2)
    num -= 1 # 對此公共變數進行-1操作

thread_list = []
for i in range(100):
    t = threading.Thread(target=subNum)
    t.start()
    thread_list.append(t)

for t in thread_list: # 等待所有執行緒執行完畢
    t.join()

print(`final num:`, num)
# 加鎖版本

def subNum():
    global num  # 在每個執行緒中都獲取這個全域性變數
    print(`--get num:`, num)
    time.sleep(1)
    lock.acquire()  # 修改資料前加鎖
    num -= 1  # 對此公共變數進行-1操作
    lock.release()  # 修改後釋放


num = 100  # 設定一個共享變數
thread_list = []
lock = threading.Lock()  # 生成全域性鎖
for i in range(100):
    t = threading.Thread(target=subNum)
    t.start()
    thread_list.append(t)

for t in thread_list:  # 等待所有執行緒執行完畢
    t.join()

print(`final num:`, num)

Rlock與Lock的區別:

RLock允許在同一執行緒中被多次acquire。而Lock卻不允許這種情況。否則會出現死迴圈,程式不知道解哪一把鎖。注意:如果使用RLock,那麼acquire和release必須成對出現,即呼叫了n次acquire,必須呼叫n次的release才能真正釋放所佔用的鎖

Events

Python提供了Event物件用於執行緒間通訊,它是由執行緒設定的訊號標誌,如果訊號標誌位真,則其他執行緒等待直到訊號接觸。

Event物件實現了簡單的執行緒通訊機制,它提供了設定訊號,清除訊號,等待等用於實現執行緒間的通訊。

event = threading.Event() 建立一個event

1 設定訊號
event.set()

使用Event的set()方法可以設定Event物件內部的訊號標誌為真。Event物件提供了isSet()方法來判斷其內部訊號標誌的狀態。
當使用event物件的set()方法後,isSet()方法返回真

2 清除訊號
event.clear()

使用Event物件的clear()方法可以清除Event物件內部的訊號標誌,即將其設為假,當使用Event的clear方法後,isSet()方法返回假

3 等待
event.wait()

Event物件wait的方法只有在內部訊號為真的時候才會很快的執行並完成返回。當Event物件的內部訊號標誌位假時,
則wait方法一直等待到其為真時才返回。也就是說必須set新號標誌位真

def do(event):
    print(`start`)
    event.wait()
    print(`execute`)

event_obj = threading.Event()
for i in range(10):
    t = threading.Thread(target=do, args=(event_obj,))
    t.start()

event_obj.clear()
inp = input(`輸入內容:`)
if inp == `true`:
    event_obj.set()

相關文章