信號量(Semaphore)
之前講的線程鎖(互斥鎖) 同時只允許一個線程更改數據,而Semaphore是同時允許一定數量的線程更改數據 ,比如廁所有3個坑,那最多只允許3個人上廁所,后面的人只能等里面有人出來了才能再進去。
1、信號量
- 是一個變量,控制着對公共資源或者臨界區的訪問。信號量維護着一個計數器,指定可同時訪問資源或者進入臨界區的線程數。
- 每次有一個線程獲得信號量時,計數器-1。若計數器為0,其他線程就停止訪問信號量,直到另一個線程釋放信號量
說白了就是在同一時間,可以只允許設定的數量的線程去執行
import threading import time def run(n): semaphore.acquire() # 加信號量鎖 time.sleep(5) print("run the thread: %s\n" % n) semaphore.release() # 釋放信號量鎖 if __name__ == '__main__': semaphore = threading.BoundedSemaphore(5) # 最多允許5個線程同時運行(Bounded:綁定,Semaphore:信號量) for i in range(20): t = threading.Thread(target=run, args=(i,)) t.start() while threading.active_count() != 1: pass else: print('----all threads done---')
上面程序的執行,會讓人感覺是:分了4組,前5個同時完成,然后又5個同時進去。但是實際的效果是:這5個里面如果有3個完成,就會立刻再放3個進去。不會等5個都完成,每出來1個就放進去1個,出來幾個放進去幾個
2、使用場景和總結
- 連接池,線程池,MySQL的有連接池,同一時間有多少個並發,就能連多少個連接。
- 我們為了保證我的socket_server,因為python不會默認現在你啟動多少個線程,但是你啟動的線程越多,就會把系統拉的越慢,就會把程序拉的越慢。這里就可以搞一個我同一時間放100線程個進來,就是用semaphore
- python3.x 雖然不加鎖也是正確的,但是最好還是把鎖加上
event(紅綠燈例子)
日常生活中經常遇到紅綠燈,我們就很好理解紅綠燈的例子,就是紅燈停,綠燈行。
現在生成一個線程,這個線程我讓它扮演紅綠燈,它每過一段時間就變成綠燈,又過一段時間變成紅燈,又變成黃燈。然后再生成3-5個線程作為車。車看見紅燈,它就停下來等着,如果說是綠燈,車子就走。所以就涉及到紅燈這個線程,紅綠燈的這個線程就跟車線程之前產生了依賴了。就是紅綠燈這個線程必須在綠燈的時候才能走,在紅燈的時候就立刻停下來。所以互相之前,一個線程會根據另外一個線程的狀態產生一些變化。類似這種場景的實現,就是:event,即事件。
1、事件的基礎內置函數
event = threading.Event() # 設置一個事件的全局變量 event.is_set()# 判斷是否已經設置標志位。 event.wait() # 沒有設置標志位的時候會阻塞,一遇到標志位就不會阻塞 #判斷是否已經設置標志位。 event.set() # 設置標志位 ,標志位設置了,代表着綠燈,直接通行。 event.clear() # 清除標志位,標志位被清空,代表紅燈,wait等待變綠燈。
2、紅綠燈例子
import threading import time event = threading.Event() # 生成線程事件實例 def lighter(): """ 模擬紅綠燈 :return: """ count = 0 event.set() # 先設置標志位,代表綠燈 while True: if count > 5 and count < 10: # 改成紅燈 event.clear() # 清除標志位,變成紅燈 print("\033[41m red light is on ....\033[0m") elif count > 10: event.set() # 創建標志位,變成綠燈 count = 0 else: print("\033[42m green light is on ....\033[0m") time.sleep(1) count += 1 def car(name): """ 模擬車子 :param name: :return: """ while True: if event.is_set(): # 有標志位,代表是綠燈 print("{0} running ....".format(name)) time.sleep(1) else: # 如果不是綠燈就代表紅燈 print("{0} sees red light ,waiting ....".format(name)) event.wait() # 阻塞 print("\033[32m green light is on , start going ...\033[0m") light = threading.Thread(target=lighter,) # 啟動代表紅綠燈的線程 light.start() car1 = threading.Thread(target=car, args=("car1",)) # 啟動代表車的線程 car1.start()
3、員工刷卡例子
員工進公司門要刷卡, 我們這里設置一個線程是“門”, 再設置幾個線程為“員工”,員工看到門沒打開,就刷卡,刷完卡,門開了,員工就可以通過。
import threading import time import random def door(): door_open_time_counter = 0 while True: if door_swiping_event.is_set(): print("\033[32;1m door opening....\033[0m") door_open_time_counter +=1 else: print("\033[31;1m door closed...., swipe to open.\033[0m") door_open_time_counter = 0 # 清空計時器 door_swiping_event.wait() if door_open_time_counter > 3: # 門開了已經3s了,該關了 door_swiping_event.clear() time.sleep(0.5) def staff(n): print("staff [%s] is coming..." % n ) while True: if door_swiping_event.is_set(): print("\033[34;1m door is opened, passing.....\033[0m") break else: print("staff [%s] sees door got closed, swiping the card....." % n) print(door_swiping_event.set()) door_swiping_event.set() print("after set ", door_swiping_event.set()) time.sleep(0.5) if __name__ == "__main__": door_swiping_event = threading.Event() # 設置事件 door_thread = threading.Thread(target=door) door_thread.start() for i in range(20): p = threading.Thread(target=staff, args=(i,)) time.sleep(random.randrange(3)) p.start()