在多執行緒和高併發的環境下,我們經常會遇到需要確保程式碼段互斥執行的場景。比如,在電商平臺中,當多個使用者同時購買同一件商品時,如何確保庫存的扣減是執行緒安全的?
今天,我們將一起探討這個問題,並介紹一個名為 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
提供了多種鎖機制,以適應不同的應用場景:
- 檔案鎖(flock):適用於單伺服器環境。
- 分散式鎖(redisLock):適用於需要跨多個伺服器或例項的分散式環境。
- 紅鎖(redLock):適用於 Redis 叢集環境,提供更高的可靠性。
- 協程級別的互斥鎖(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 呀~