【python內功修煉008】:信號量Semaphore和Event事件

一、信號量介紹


1.1 什麼是信號量

信號量可以理解爲多把鎖,同時允許多個線程來更改數據。而互斥鎖同時只允許一個線程更改數據。

1.2 信號量的作用

信號量的使用主要是用來保護共享資源,使得資源在一個時刻只有一個進程(線程)所擁有。

信號量的主要用途是用來控制線程的併發量的,Semaphore管理一個內置的計數器,每調用一次acquire()方法時,計數器-1,每調用一次release()方法時,內部計數器+1。

不過需要注意的是,Semaphore內部的計數器不能小於0!當它內部的計數器等於0的時候,這個線程會被鎖定,進入阻塞狀態,直到其他線程去調用release方法。

1.3 信號量和互斥鎖的區別

信號量是用在多線程多任務 同步 的,互斥鎖是用在多線程多任務 **互斥 ** 的

  • 信號量可以做到一個線程完成了某個動作就通過信號量告訴別的線程,別的線程再進行某些動作。
  • 互斥鎖是指一個線程使用某個資源通過對其加鎖而使得其他線程無法訪問,直到這個線程解鎖,其他線程纔可以繼續訪問。

二、信號量用法


2.1 信號量(Semaphore)語法

s=Semaphore(value)

創建一個新的信號量。value是計數器的初始值。如果省略value,將默認設置爲1

2.2 Semaphore常用方法

s.acquire(blocking):獲取信號量。

如果內部計數器大於0,此方法將把它的值減1。
然後立即返回。如果它的值爲0,此方法將阻塞,直到另一個線程調用release()方法爲止。
blocking參數的行爲與Lock和RLock對象中描述的相同。

s.release():通過將內部計數器的值加1來釋放一個信號量。
如果計數器爲0,而且另一個線程正在等待,該線程講被喚醒。
如果有多個線程正在等待,只能從它的acquire()的調用中返回一個,並且順序不確定。

2.3 Semaphore代碼實例

import time
from threading import current_thread, Semaphore, active_count, Thread

def run():
    semaphore.acquire()   # 獲取信號量
    time.sleep(1)   # 每隔1秒彈出一次運行結果
    print('線程:%s ' % current_thread().getName())

    semaphore.release()    # 釋放信號量

if __name__=='__main__':
    semaphore = Semaphore(5)   # 聲明semaphore實例,每次允許5個線程同時運行
    for i in range(30):     # 定義30個線程
        t = Thread(target=run)
        t.start()
while active_count != 1:   # 作用相當於join()等待線程執行完畢,如果活躍數爲1,則說明只有主線程
    pass
else:
    print("---所有線程都完成了")

三、Event介紹


3.1 Event誕生的背景

通俗的例子:

在紅路燈交通系統中,汽車能根據紅綠燈情況選擇是否通行;將交通系統代入到程序中,紅路燈交通系統則是一個程序任務,汽車和紅路燈是運行這個程序的線程。如何讓汽車(N個)線程,來根據紅路燈(1個)線程的結果來運行或者停止任務呢?這就是event控制多線程方案

Python threading模塊不同於其它語言之處在於它沒有提供線程的終止方法,而且python的多線程設計本身也是不希望用戶這麼做,但是很多時候我們得到某個結果後爲了節省不必要的資源 必須停止其他線程的工作。

其實如果如果想要實現這個功能很簡單,我們可以自己定義一個全局變量,在每次任務循環中判斷這個變量的狀態,如果某個線程中得到結果,那麼改變變量,其他線程得知這個變量改變後break即可。

四、Event的用法


Event對象實現了簡單的線程通信機制,它提供了設置信號,清除信號,等待等用於實現線程間的通信。

事件處理的機制:全局定義了一個“Flag”,如果“Flag”值爲False,那麼當程序執行event.wait方法時就會阻塞,如果“Flag”值爲True,那麼event.wait方法時便不再阻塞。clear:將“Flag”設置爲Falseset:將“Flag”設置爲True

4.1 Event對象方法

方法 作用 功能
set() 設置信號 默認設置Event對象內部的信號標誌爲真。Event對象提供了isSet()方法來判斷其內部信號標誌的狀態,當使用event對象的set()方法後,isSet()方法返回真.
clear() 清除信號 可以清除Event對象內部的信號標誌,即將其設爲假,當使用Event的clear方法後,isSet()方法返回假
wait(timeout=None) 阻塞信號 Event對象wait的方法只有在內部信號爲真的時候纔會很快的執行並完成返回。當Event對象的內部信號標誌位假時,則wait方法一直等待到其爲真時才返回。
is_set() 返回信號 該方法返回 Event 的內部旗標是否爲True

4.2 Event代碼實例

'''

    模擬紅路燈

    python線程的事件用於主線程控制其他線程的執行,事件主要提供了三個方法
set、wait、clear。

事件處理的機制:全局定義了一個“Flag”,如果“Flag”值爲
False,那麼當程序執行
event.wait
方法時就會阻塞,如果“Flag”值爲True,那麼event.wait
方法時便不再阻塞。

clear:將“Flag”設置爲False
set:將“Flag”設置爲True
'''

# is_set():該方法返回 Event 的內部旗標是否爲True。
# set():該方法將會把 Event 的內部旗標設置爲 True,並喚醒所有處於等待狀態的線程。
# clear():該方法將 Event 的內部旗標設置爲 False,通常接下來會調用 wait() 方法來阻塞當前線程。
# wait(timeout=None):該方法會阻塞當前線程。

from threading import Thread,current_thread, Event
import time, random

def Car(e):
    while True:
        if not e.is_set():  # Flase
            print('\033[31m紅燈亮\033[0m,car--%s等着' % current_thread().getName())
            e.wait()  # 第一次 阻塞
            print('\033[32m車--%s 看見綠燈亮了\033[0m' % current_thread().getName())
            time.sleep(random.randint(3, 6))
            if not e.is_set():
                print(e.is_set())
                continue
            print('走你,car--%s' % current_thread().getName())
            break


def traffic_lights(e, inverval):
    while True:
        time.sleep(inverval)
        if e.is_set():  # 判斷e的狀態
            e.clear()  # e.is_set() ---->False
        else:
            e.set()   # Event 的內部旗標設置爲 True


if __name__ == '__main__':
    e = Event()
    for i in range(3):
        p = Thread(target=Car, args=(e,))
        p.start()

    t = Thread(target=traffic_lights, args=(e, 10))
    t.start()

    print('============》')


結果:

紅燈亮,car--Thread-1等着
紅燈亮,car--Thread-2等着
紅燈亮,car--Thread-3等着
============》
車--Thread-1 看見綠燈亮了車--Thread-2 看見綠燈亮了車--Thread-3 看見綠燈亮了


走你,car--Thread-3走你,car--Thread-2

走你,car--Thread-1
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章