PHP 互斥鎖:如何確保程式碼的執行緒安全?

左诗右码發表於2024-11-27

在多執行緒和高併發的環境下,我們經常會遇到需要確保程式碼段互斥執行的場景。比如,在電商平臺中,當多個使用者同時購買同一件商品時,如何確保庫存的扣減是執行緒安全的?

今天,我們將一起探討這個問題,並介紹一個名為 wise-locksmith 的 PHP 互斥鎖庫,它可以幫助我們輕鬆地解決這類問題。

程式碼的執行緒安全

在沒有互斥機制的情況下,多個程序或執行緒可能會同時修改同一個資源,導致資料不一致的問題。例如,在一個簡單的庫存扣減操作中:

// 假設庫存為 10
$stock = 10;

// 多個請求同時到達,每個請求都扣減庫存
for ($i = 0; $i < 20; $i++) {
    $stock--;
}
// 最終庫存可能不是我們預期的 0,而是負數

這種情況在實際開發中是不可接受的。那麼,我們如何確保在 PHP 中實現程式碼的互斥執行呢?

wise-locksmith 庫介紹

wise-locksmith 是一個 PHP 互斥鎖庫,它提供了多種鎖機制來幫助我們解決執行緒安全問題。並且這個庫不侷限於任何框架,也就是說只要是在 PHP 環境中,都可以使用

下面,我們將詳細介紹這個庫的安裝、特性、基本與高階功能,並結合實際應用場景展示其在專案中的使用。來,繼續往下看吧~

安裝

首先,我們透過 Composer 快速安裝 wise-locksmith

composer require pudongping/wise-locksmith

特性

wise-locksmith 提供了多種鎖機制,以適應不同的應用場景:

  1. 檔案鎖(flock):適用於單伺服器環境。
  2. 分散式鎖(redisLock):適用於需要跨多個伺服器或例項的分散式環境。
  3. 紅鎖(redLock):適用於 Redis 叢集環境,提供更高的可靠性。
  4. 協程級別的互斥鎖(channelLock):適用於 Swoole 協程環境。

基本功能

檔案鎖(flock)

檔案鎖沒有任何依賴。可透過可選的第 3 個引數引數設定鎖的超時時間,單位:秒。(支援浮點型,比如 1.5 表示 1500ms 也就是最多會等待 1500ms,如果沒有搶佔到鎖,那麼則主動放棄搶鎖,同時會丟擲 Pudongping\WiseLocksmith\Exception\TimeoutException 異常)
設定成 Pudongping\WiseLocksmith\Lock\File\Flock::INFINITE_TIMEOUT 時,表示永不過期,則當前一直會阻塞式搶佔鎖,直到搶佔到鎖為止。預設值為:Pudongping\WiseLocksmith\Lock\File\Flock::INFINITE_TIMEOUT

檔案鎖是最簡單的一種鎖,適用於單伺服器環境。它透過鎖定一個檔案來實現互斥。以下是一個簡單的檔案鎖示例:

<?php
require 'vendor/autoload.php';
use Pudongping\WiseLocksmith\Locker;

$path = tempnam(sys_get_temp_dir(), 'wise-locksmith-flock-');
$fileHandler = fopen($path, 'r+');
$locker = new Locker();
try {
    $locker->flock($fileHandler, function () use ($stock) {
        // 這裡寫你想保護的程式碼
        $stock--;
        // 確保操作的原子性
    });
} catch (\Exception $e) {
    // 處理異常
}
fclose($fileHandler);
unlink($path);

分散式鎖(redisLock)

需要依賴 redis 擴充套件。可透過可選的第 3 個引數設定鎖的超時時間,單位:秒。(支援浮點型,比如 1.5 表示 1500ms 也就是最多會等待 1500ms,如果沒有搶佔到鎖,那麼則主動放棄搶鎖,同時會丟擲 Pudongping\WiseLocksmith\Exception\TimeoutException 異常)
預設值為:5。第 4 個引數為當前鎖的具有唯一性的值,除非有特殊情況下需要設定,一般不需要設定。

在分散式系統中,我們經常需要確保跨多個伺服器的操作是互斥的。redisLock 提供了這樣的功能:

<?php
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$locker = new Locker();
try {
    $locker->redisLock($redis, 'redisLock', function () use ($stock) {
        // 這裡寫你想保護的程式碼
        $stock--;
        // 確保操作的原子性
    });
} catch (\Exception $e) {
    // 處理異常
}

高階功能

紅鎖(redLock)

redLock 鎖所需要設定的引數和 redisLock 鎖除了第一個引數有區別以外,其他幾個引數完全一致。redLock 鎖是 redisLock 鎖的叢集實現。

紅鎖是分散式鎖的一種高階實現,它在 Redis 叢集環境中提供更高的可靠性:

<?php
$redisInstances = [
    ['host' => '127.0.0.1', 'port' => 6379],
    // 其他 Redis 例項...
];
$redis = array_map(function ($v) {
    $redis = new \Redis();
    $redis->connect($v['host'], $v['port']);
    return $redis;
}, $redisInstances);
$locker = new Locker();
try {
    $locker->redLock($redis, 'redLock', function () use ($stock) {
        // 這裡寫你想保護的程式碼
        $stock--;
        // 確保操作的原子性
    });
} catch (\Exception $e) {
    // 處理異常
}

協程級別的互斥鎖(channelLock)

使用此鎖時,需要安裝 swoole 擴充套件。且版本必須大於等於 4.5。可透過可選的第 3 個引數設定鎖的超時時間,單位:秒。(支援浮點型,比如 1.5 表示 1500ms 也就是最多會等待 1500ms,如果沒有搶佔到鎖,那麼則主動放棄搶鎖,同時直接返回 false 表示沒有搶佔到鎖)
設定成 -1 時,表示永不過期,則當前一直會阻塞式搶佔鎖,直到搶佔到鎖為止。預設值為:-1

在 Swoole 協程環境中,channelLock 提供了協程級別的互斥鎖:

<?php
$locker = new Locker();
try {
    $locker->channelLock('channelLock', function () use ($stock) {
        // 這裡寫你想保護的程式碼
        $stock--;
        // 確保操作的原子性
    });
} catch (\Exception $e) {
    // 處理異常
}

實際應用場景

假設我們有一個高併發的電商平臺,需要在使用者下單時扣減庫存。使用 wise-locksmith 庫,我們可以確保在任何時候只有一個請求能夠修改庫存,從而避免超賣的問題。以下是如何在實際專案中使用 wise-locksmith 來實現庫存扣減的互斥操作:

<?php
// 假設我們有一個全域性的 Redis 連線例項
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);

// 庫存扣減操作
function decreaseStock($productId, $quantity) {
    $locker = new Locker();
    try {
        $locker->redisLock($redis, "stock_lock_{$productId}", function () use ($productId, $quantity) {
            // 這裡寫你想保護的程式碼
            // 假設我們從資料庫中獲取當前庫存
            $stock = getStockFromDatabase($productId);
            if ($stock >= $quantity) {
                // 更新庫存
                updateStockInDatabase($productId, $stock - $quantity);
            }
        });
    } catch (\Exception $e) {
        // 處理異常
    }
}

// 呼叫扣減庫存函式
decreaseStock(123, 1);

結語

透過 wise-locksmith 庫,我們可以輕鬆地在 PHP 應用中實現程式碼的互斥執行,無論是單伺服器環境還是分散式系統。

希望這篇文章能幫助你更好地理解和使用 wise-locksmith 庫,確保你的程式碼在多執行緒環境下的執行緒安全。如果你覺得這個庫對你有點兒幫助,那就請幫忙點個 Star 呀~

相關文章