Python並行程式設計(三):多執行緒同步之semaphore(訊號量)實現簡易生產者-消費者模型

若數發表於2019-04-10

什麼是訊號量

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.')

複製程式碼

執行截圖如下:

執行結果

相關文章