1、某進程1執行 SETNX lock 以嘗試獲取鎖 2、由於某進程2已獲得了鎖,所以進程1執行 SETNX lock 返回0,即獲取鎖失敗 3、進程1執行 GET lock 來檢測鎖是否已超時,如果沒超時,則線程等待一段時間,再次檢測 4、如果進程1檢測到鎖已超時,即當前的時間大於鍵 lock 的 ...
1、某進程1執行 SETNX lock 以嘗試獲取鎖
2、由於某進程2已獲得了鎖,所以進程1執行 SETNX lock 返回0,即獲取鎖失敗
3、進程1執行 GET lock 來檢測鎖是否已超時,如果沒超時,則線程等待一段時間,再次檢測
4、如果進程1檢測到鎖已超時,即當前的時間大於鍵 lock 的值,進程1會執行以下操作
GETSET lock <current Unix timestamp + lock timeout + 1>
5、由於 GETSET 操作在設置鍵的值的同時,還會返回鍵的舊值,通過比較鍵 lock 的舊值是否小於當前時間,可以判斷進程是否已獲得鎖
6、假如另一個進程3也檢測到鎖已超時,併在進程1之前執行了 GETSET 操作,那麼進程1的 GETSET 操作返回的是一個大於當前時間的時間戳,這樣進程1就不會獲得鎖而繼續等待。註意到,即使進程1接下來將鍵 lock 的值設置了比進程3設置的更大的值也沒影響。
另外,值得註意的是,在進程釋放鎖,即執行 DEL lock 操作前,需要先判斷鎖是否已超時。如果鎖已超時,那麼鎖可能已由其他進程獲得,這時直接執行 DEL lock 操作會導致把其他進程已獲得的鎖釋放掉。
C# Code
using System;
using System.Threading;
using System.Threading.Tasks;
using CSRedis;
namespace RedisLockDemo
{
public class CsRedisLock
{
private static readonly int _lock_timeout = 40;
private static readonly string _lock_key = "lock";
public static void Test()
{
var rds = new CSRedisClient("127.0.0.1:6379,password=123456,defaultDatabase=13,poolsize=50,ssl=false");
RedisHelper.Initialization(rds);
Parallel.For(0, 13, x =>
{
if (GetLock(_lock_key))
{
Console.WriteLine($"person:{x},線程ID:{Thread.CurrentThread.ManagedThreadId},獲得鎖 woking");
if (DateTimeOffset.Now.ToUnixTimeMilliseconds() < RedisHelper.Get<long>(_lock_key))
{
//釋放鎖
RedisHelper.Del(_lock_key);
}
}
else
{
Console.WriteLine($"person:{x},線程ID:{Thread.CurrentThread.ManagedThreadId},獲取鎖異常");
}
});
Console.WriteLine();
}
private static bool GetLock(string key)
{
bool getLocked = false;
try
{
while (!getLocked)
{
var now = DateTimeOffset.Now.ToUnixTimeMilliseconds();
var lock_time = now + _lock_timeout + 1;
getLocked = RedisHelper.SetNx(key, lock_time);
//判斷是否獲取鎖,
if (getLocked || now > RedisHelper.Get<long>(key) && now > RedisHelper.GetSet<long>(key, lock_time))
{
getLocked = true;
}
else
{
Thread.Sleep(30);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return getLocked;
}
}
}
相關文檔:https://redis.io/commands/setnx