什麼是訊號量
semaphore
訊號量,其控制著對公共資源或者臨界區的訪問。訊號量維護著一個計數器,指定可同時訪問資源或者進入臨界區的執行緒數。
semaphore是一個內建的計數器
每當呼叫acquire()時,內建計數器-1;如果計數器為負數,即資源正在被佔用,需要掛起等待
每當呼叫release()時,內建計數器+1;增加到正數時,佇列中的第一個等待執行緒就可以訪問共享資源了
模擬Lock()鎖機制
如果我們將計數器設定為1即,第一次執行緒是不需要等待訊號量的釋放的,參照上節程式碼可以進行對比:
import threading
import time
resoure = 0
count = 1000000
semaphore = threading.Semaphore(1)
def increment():
global resoure
for i in range(count):
semaphore.acquire()
resoure += 1
semaphore.release()
def decerment():
global resoure
for i in range(count):
semaphore.acquire()
resoure -= 1
semaphore.release()
increment_thread = threading.Thread(target=increment)
decerment_thread = threading.Thread(target=decerment)
increment_thread.start()
decerment_thread.start()
increment_thread.join()
decerment_thread.join()
print(resoure)
複製程式碼
簡易生產者-消費者模型
import threading
import random
import time
semaphore = threading.Semaphore(0)
# 假設生產的資源
item_number = 0
# 消費者
def consumer():
print('Consumer is waiting for Producer')
# 等待獲取訊號量
semaphore.acquire()
print('get the product , number is {}'.format(item_number))
# 生產者
def producer():
global item_number
# 模擬生產資源過程
time.sleep(2)
item_number = random.randint(1, 100)
time.sleep(2)
print('made the product , number is {}'.format(item_number))
# 釋放訊號量
semaphore.release()
if __name__ == "__main__":
for i in range(5):
# 將生產者、消費者例項化為執行緒
thread_consumer = threading.Thread(target=consumer)
thread_producer = threading.Thread(target=producer)
thread_consumer.start()
thread_producer.start()
thread_consumer.join()
thread_producer.join()
print('consumer-producer example end.')
複製程式碼
執行截圖如下:
我們可以看見兩個執行緒執行時的規律,即消費者必須等待生產者生產好商品(即釋放資源),消費者才能獲取消費資源(即訪問資源),其餘時間消費者執行緒都處於掛起等待(等待訊號量)。利用訊號量控制併發數量
利用semaphore
我們就可以設定同時訪問某些共享資源的執行緒數量,即通過設設定訊號量的值來控制執行緒同時訪問的數量,比如我們可以控制爬蟲程式訪問連結的執行緒數量(似乎這樣可以實現一定的非同步),減少目標網站的壓力,同時訊號量也支援上下文管理器:
import threading
import random
import time
# 訊號量為三即能夠釋放的資源為三次
semaphore = threading.Semaphore(3) # 互斥鎖+佇列 相當於一個容器,容器裡同時最大可以存在五個鑰匙,同時也只能有五個執行緒,
# 誰先拿到並釋放後,下一個才能拿到鑰匙
# 假定url序號
order = 0
def spider():
global order
with semaphore:
# 模擬採集過程
time.sleep(2)
order +=1
print('{} is crawlering on {}th url'.format(threading.currentThread().getName(), order))
time.sleep(2)
Threads = []
for i in range(10):
t = threading.Thread(target=spider)
Threads.append(t)
t.start()
for t in Threads:
t.join()
print('Spider end.')
複製程式碼
執行截圖如下: