Semaphore使用詳解

一、Semaphore基礎知識

1、Semaphore的定義和概念

import threading

sem = threading.Semaphore(3)

Semaphore是一種計數器,它用來保護對共享資源的訪問。在任何時刻,同時只能有一個線程訪問共享資源。當線程要訪問共享資源時,它必須首先獲得Semaphore,如果Semaphore計數器的值為0,那麼線程就會被阻塞,直到Semaphore有信號為止。

2、Semaphore的構造函數

__init__(self, value=1)

Semaphore的構造函數有一個參數value,這個參數指定了Semaphore的初始計數器的值,默認為1。

3、Semaphore的方法

Semaphore對象有兩個主要方法,分別是acquire()和release()方法。

  • acquire([blocking])

    acquire()方法嘗試獲取Semaphore,如果Semaphore計數器的值為0,就會阻塞線程。可選參數blocking默認為1,如果設為0,則acquire()方法會立即返回,不會阻塞線程。當acquire()方法成功獲取Semaphore時,Semaphore計數器的值會減1。

  • release()

    release()方法會把Semaphore的計數器的值加1,如果此時Semaphore有阻塞的線程,它就會選擇一個線程並喚醒它。

二、Semaphore使用場景

1、實現對公共資源的訪問控制

Semaphore的最主要的應用場景就是實現對公共資源的訪問控制。Semaphore控制對公共資源的訪問,保證同一時刻只有一個線程在訪問該資源,而其他線程必須等待。

import threading

class SharedResource:
    def __init__(self):
        self.sem = threading.Semaphore(1)
        self.data = None
    
    def get(self):
        self.sem.acquire()
        result = self.data
        self.sem.release()
        return result
    
    def set(self, data):
        self.sem.acquire()
        self.data = data
        self.sem.release()

上述代碼中,定義了一個SharedResource類,該類有一個Semaphore成員變數sem,Semaphore的計數器的初始值為1。類中定義了get()和set()方法,這兩個方法在訪問data成員變數時,都要先獲取Semaphore,然後再進行操作。

2、控制程序並發訪問線程數

當程序需要同時運行大量的線程的時候,如果沒有控制線程數,容易出現線程過多造成CPU調度產生的負擔,進而導致程序運行緩慢或者崩潰。這時,可以使用Semaphore來控制並發訪問線程數,保證程序有一個平緩的工作負荷。

import threading
import time

sem = threading.Semaphore(3)

def worker():
    with sem:
        print(f'Thread {threading.get_ident()} started')
        time.sleep(1)
        print(f'Thread {threading.get_ident()} finished')
        
for i in range(5):
    t = threading.Thread(target=worker)
    t.start()

上述代碼中,創建了5個線程來執行worker函數。設置了Semaphore的計數器初始值為3,即同一時間只能有3個線程執行worker函數。使用with語句獲取Semaphore,當3個線程都在執行時,後續的線程會被阻塞。當某個線程執行結束時,會釋放Semaphore,喚醒被阻塞的線程。

三、Semaphore實現生產者-消費者模型

Semaphore還可以用來實現生產者-消費者模型。生產者和消費者之間通過一個隊列來進行通訊。當隊列中沒有可以消費的數據時,消費者線程會阻塞,當隊列滿了時,生產者線程會阻塞。當生產者生產了新的數據時,會通知阻塞的消費者線程開始消費,當消費者消費了數據時,會通知阻塞的生產者線程開始生產。

import threading
import time

queue = []
sem_producer = threading.Semaphore(10)
sem_consumer = threading.Semaphore(0)
mutex = threading.Lock()

class ProducerThread(threading.Thread):
    def __init__(self, name):
        super().__init__(name=name)
    
    def run(self):
        global queue
        for i in range(20):
            sem_producer.acquire()
            mutex.acquire()
            queue.append(i)
            print(f'{self.name} produced {i}', end='\n' if i%10==9 else ', ')
            mutex.release()
            sem_consumer.release()
            time.sleep(0.1)
        
class ConsumerThread(threading.Thread):
    def __init__(self, name):
        super().__init__(name=name)
    
    def run(self):
        global queue
        while True:
            sem_consumer.acquire()
            mutex.acquire()
            if len(queue) == 0:
                mutex.release()
                break
            i = queue.pop(0)
            print(f'{self.name} consumed {i}', end='\n' if i%10==9 else ', ')
            mutex.release()
            sem_producer.release()
            time.sleep(0.1)

producer_threads = [ProducerThread(f'Producer {i}') for i in range(3)]
consumer_threads = [ConsumerThread(f'Consumer {i}') for i in range(2)]

for thread in producer_threads + consumer_threads:
    thread.start()

for thread in producer_threads + consumer_threads:
    thread.join()

上述代碼中,定義了ProducerThread和ConsumerThread兩個線程類,ProducerThread負責生產數據,ConsumerThread負責消費數據。queue是一個列表,表示共享數據的隊列,Semaphore sem_producer的計數器初始值為10,Semaphore sem_consumer的計數器初始值為0,互斥鎖mutex保護對隊列的訪問。運行3個生產者線程和2個消費者線程,生產者生產20個數據,消費者消費這20個數據。

四、總結

Semaphore是Python多線程編程中非常有用的工具,可以用來控制對公共資源的訪問,也可以用來控制並發線程數,還能夠實現生產者-消費者模型。但是,使用Semaphore時需要注意兩個問題:第一,互斥鎖和Semaphore的區別,互斥鎖是為了保證對共享資源的互斥訪問,Semaphore是為了控制對公共資源的訪問。第二,使用Semaphore時需要注意死鎖問題,當某個線程無法獲取Semaphore時會被阻塞,如果在某個線程獲取Semaphore之前另一個線程已經獲取了Semaphore並陷入阻塞,就會出現死鎖,程序將無法繼續運行。

原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/154115.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
小藍的頭像小藍
上一篇 2024-11-15 03:26
下一篇 2024-11-15 03:26

相關推薦

  • 神經網路代碼詳解

    神經網路作為一種人工智慧技術,被廣泛應用於語音識別、圖像識別、自然語言處理等領域。而神經網路的模型編寫,離不開代碼。本文將從多個方面詳細闡述神經網路模型編寫的代碼技術。 一、神經網…

    編程 2025-04-25
  • Linux sync詳解

    一、sync概述 sync是Linux中一個非常重要的命令,它可以將文件系統緩存中的內容,強制寫入磁碟中。在執行sync之前,所有的文件系統更新將不會立即寫入磁碟,而是先緩存在內存…

    編程 2025-04-25
  • Python輸入輸出詳解

    一、文件讀寫 Python中文件的讀寫操作是必不可少的基本技能之一。讀寫文件分別使用open()函數中的’r’和’w’參數,讀取文件…

    編程 2025-04-25
  • Python安裝OS庫詳解

    一、OS簡介 OS庫是Python標準庫的一部分,它提供了跨平台的操作系統功能,使得Python可以進行文件操作、進程管理、環境變數讀取等系統級操作。 OS庫中包含了大量的文件和目…

    編程 2025-04-25
  • MPU6050工作原理詳解

    一、什麼是MPU6050 MPU6050是一種六軸慣性感測器,能夠同時測量加速度和角速度。它由三個感測器組成:一個三軸加速度計和一個三軸陀螺儀。這個組合提供了非常精細的姿態解算,其…

    編程 2025-04-25
  • nginx與apache應用開發詳解

    一、概述 nginx和apache都是常見的web伺服器。nginx是一個高性能的反向代理web伺服器,將負載均衡和緩存集成在了一起,可以動靜分離。apache是一個可擴展的web…

    編程 2025-04-25
  • 詳解eclipse設置

    一、安裝與基礎設置 1、下載eclipse並進行安裝。 2、打開eclipse,選擇對應的工作空間路徑。 File -> Switch Workspace -> [選擇…

    編程 2025-04-25
  • git config user.name的詳解

    一、為什麼要使用git config user.name? git是一個非常流行的分散式版本控制系統,很多程序員都會用到它。在使用git commit提交代碼時,需要記錄commi…

    編程 2025-04-25
  • Java BigDecimal 精度詳解

    一、基礎概念 Java BigDecimal 是一個用於高精度計算的類。普通的 double 或 float 類型只能精確表示有限的數字,而對於需要高精度計算的場景,BigDeci…

    編程 2025-04-25
  • Linux修改文件名命令詳解

    在Linux系統中,修改文件名是一個很常見的操作。Linux提供了多種方式來修改文件名,這篇文章將介紹Linux修改文件名的詳細操作。 一、mv命令 mv命令是Linux下的常用命…

    編程 2025-04-25

發表回復

登錄後才能評論