C#实操控制并发之Lock和分布式锁
其实就是一把锁,在分布式环境下,多个客户端并发访问的情况下,保证共享资源的串行有序访问,控制并发。Redis分布式锁底层采用setnx+expire命令组合来实现加锁,释放锁时,根据身份标识去释放各自创建的锁,各删各的锁。基于上面的问题,根据Redis来实现的Redis分布式锁就应允而生。因为Redis够快、支持事务、单命令支持原子性等这些特性,保证了Redis分布式锁的出色性能。但是个人很容易混
1.背景
总是会听到分布式锁、并发等问题。但是个人很容易混淆,不易深刻理解这个锁对于并发的控制。
本文是个人专门用来记录对以上这些问题的理解过程。
在C#中有个关键字Lock.我们一般会用来给临界区或者共享资源来加锁使用,实现同一时间只有1个线程来访问。ps:lock是编译器的语法糖,底层是Monitor.Enter+Monitor.Exit等功能。但是lock只能保证同一进程下多个线程的并发访问。如果这时开了多个客户端来访问,就会出现并发问题。所以lock只适合单节点下的并发情况。
基于上面的问题,根据Redis来实现的Redis分布式锁就应允而生。因为Redis够快、支持事务、单命令支持原子性等这些特性,保证了Redis分布式锁的出色性能。
Redis分布式锁是什么了?其实就是一把锁,在分布式环境下,多个客户端并发访问的情况下,保证共享资源的串行有序访问,控制并发。Redis分布式锁底层采用setnx+expire命令组合来实现加锁,释放锁时,根据身份标识去释放各自创建的锁,各删各的锁。
本文会以秒杀为案例,简单记录下不同场景下的并发。
2.场景分析
2.1 同步方式执行秒杀
程序如下:
/// <summary>
/// 同步的方式执行秒杀
/// </summary>
private static void TestSeckillSync()
{
//模拟线程数
var thredNumber = 20;
//秒杀库存数
var stockNumber = 3;
//秒杀成功队列key
var key = "order_queue";
var csredis = new CSRedisClient(redisConnectionStr);
csredis.Del(key);
var isEnd = false;
// 创建秒杀执行信号量集合
List<Task> taskList = new List<Task>();
// 添加计时器
Stopwatch stopwatch = new Stopwatch();
// 开启
stopwatch.Start();
for (int i = 0; i < thredNumber; i++)
{
int number = i;
Thread.Sleep(50);
if (isEnd)
{
Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId} - 用户{number} 秒杀失败,抢完了。");
}
var len = csredis.LLen(key);
//库存不足
if (len >= stockNumber)
{
isEnd = true;
stopwatch.Stop();
Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId} - 用户{number} 秒杀失败,抢完了。");
}
else
{
var value = $"线程{Thread.CurrentThread.ManagedThreadId}-用户{number}";
csredis.LPush(key, value);
Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId} - 用户{number} 秒杀成功。");
}
}
// 等待所有秒杀列表中任务结束
var lenALL = csredis.LLen(key);
Console.WriteLine($"\r\n秒杀成功人数:{lenALL} 人,用时:{stopwatch.ElapsedMilliseconds} 毫秒.");
Console.WriteLine($"\r\n是否超售:{(lenALL > stockNumber ? "是" : "否")}");
Console.WriteLine("\r\n秒杀成功人员名单:");
for (int i = 0; i < stockNumber; i++)
{
Console.WriteLine(csredis.RPop(key));
}
}
程序执行后的结果:
2.2 并发方式执行秒杀
/// <summary>
/// 并发方式执行秒杀
/// </summary>
private static void TestSeckillTask()
{
//模拟线程数
var thredNumber = 20;
//秒杀库存数
var stockNumber = 3;
//秒杀成功队列key
var key = "order_queue";
var csredis = new CSRedisClient(redisConnectionStr);
csredis.Del(key);
var isEnd = false;
// 创建秒杀执行信号量集合
List<Task> taskList = new List<Task>();
// 添加计时器
Stopwatch stopwatch = new Stopwatch();
// 开启
stopwatch.Start();
for (int i = 0; i < thredNumber; i++)
{
int number = i;
taskList.Add(Task.Run(() =>
{
Thread.Sleep(50);
if (isEnd)
{
Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId} - 用户{number} 秒杀失败,抢完了。");
}
var len = csredis.LLen(key);
//库存不足
if (len >= stockNumber)
{
isEnd = true;
stopwatch.Stop();
Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId} - 用户{number} 秒杀失败,抢完了。");
}
else
{
var value = $"线程{Thread.CurrentThread.ManagedThreadId}-用户{number}";
csredis.LPush(key, value);
Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId} - 用户{number} 秒杀成功。");
}
}));
}
// 等待所有秒杀列表中任务结束
Task.WaitAll(taskList.ToArray());
var lenALL = csredis.LLen(key);
Console.WriteLine($"\r\n秒杀成功人数:{lenALL} 人,用时:{stopwatch.ElapsedMilliseconds} 毫秒.");
Console.WriteLine($"\r\n是否超售:{(lenALL > stockNumber ? "是" : "否")}");
Console.WriteLine("\r\n秒杀成功人员名单:");
for (int i = 0; i < stockNumber; i++)
{
Console.WriteLine(csredis.RPop(key));
}
}
2.3 并发方式执行秒杀 加Lock
private static readonly object fileLock = new object();
/// <summary>
/// 并发方式执行秒杀+加Lock(该方式仅仅对单节点有用 控制并发)
/// </summary>
private static void TestSeckillCodeLock()
{
//模拟线程数
var thredNumber = 20;
//秒杀库存数
var stockNumber = 3;
//秒杀成功队列key
var key = "order_queue";
var csredis = new CSRedisClient(redisConnectionStr);
csredis.Del(key);
var isEnd = false;
// 创建秒杀执行信号量集合
List<Task> taskList = new List<Task>();
// 添加计时器
Stopwatch stopwatch = new Stopwatch();
// 开启
stopwatch.Start();
for (int i = 0; i < thredNumber; i++)
{
Thread.Sleep(50);
int number = i;
taskList.Add(Task.Run(() =>
{
lock (fileLock)
{
if (isEnd)
{
Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId} - 用户{number} 秒杀失败,抢完了。");
}
var len = csredis.LLen(key);
//库存不足
if (len >= stockNumber)
{
isEnd = true;
stopwatch.Stop();
Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId} - 用户{number} 秒杀失败,抢完了。");
}
else
{
var value = $"线程{Thread.CurrentThread.ManagedThreadId}-用户{number}";
csredis.LPush(key, value);
Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId} - 用户{number} 秒杀成功。");
}
}
}));
}
// 等待所有秒杀列表中任务结束
Task.WaitAll(taskList.ToArray());
var lenALL = csredis.LLen(key);
Console.WriteLine($"\r\n秒杀成功人数:{lenALL} 人,用时:{stopwatch.ElapsedMilliseconds} 毫秒.");
Console.WriteLine($"\r\n是否超售:{(lenALL > stockNumber ? "是" : "否")}");
Console.WriteLine("\r\n秒杀成功人员名单:");
for (int i = 0; i < stockNumber; i++)
{
Console.WriteLine(csredis.RPop(key));
}
}
2.4 并发方式执行秒杀+加Redis分布式锁
/// <summary>
/// 并发方式执行秒杀+加Redis分布式锁(单、多节点下控制并发)
/// </summary>
private static void TestSeckillRedisLock()
{
//模拟线程数
var thredNumber = 20;
//秒杀库存数
var stockNumber = 3;
//秒杀成功队列key
var key = "order_queue";
var lockKey = "orderlock";
var csredis = new CSRedisClient(redisConnectionStr);
csredis.Del(key);
var isEnd = false;
// 创建秒杀执行信号量集合
List<Task> taskList = new List<Task>();
// 添加计时器
Stopwatch stopwatch = new Stopwatch();
// 开启
stopwatch.Start();
for (int i = 0; i < thredNumber; i++)
{
int number = i;
taskList.Add(Task.Run(() =>
{
Thread.Sleep(50);
//尝试加锁 如果成功 再设置 过期时间
//setnx+expire 采用Lua保证命令的原子性
//也可以考虑一条命令执行 set xx xxx nx ex 1000
//设置客户端的标识,用于解锁
var nxSelfMarkvalue = $"thred{Thread.CurrentThread.ManagedThreadId}_user{number}";
//加锁
var isGetLock = csredis.RedisLock(lockKey, nxSelfMarkvalue, 1000);
if (isGetLock)
{
if (isEnd)
{
Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId} - 用户{number} 秒杀失败,抢完了。");
}
var len = csredis.LLen(key);
//库存不足
if (len >= stockNumber)
{
isEnd = true;
stopwatch.Stop();
Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId} - 用户{number} 秒杀失败,抢完了。");
}
else
{
var value = $"线程{Thread.CurrentThread.ManagedThreadId}-用户{number}";
csredis.LPush(key, value);
//解锁 nxSelfMarkvalue,防止误解锁
csredis.RedisUnLock(lockKey, nxSelfMarkvalue);
Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId} - 用户{number} 秒杀成功。");
}
}
else
{
Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId} - 用户{number} 系统繁忙,请稍后再试。 秒杀失败。");
}
}));
}
// 等待所有秒杀列表中任务结束
Task.WaitAll(taskList.ToArray());
var lenALL = csredis.LLen(key);
Console.WriteLine($"\r\n秒杀成功人数:{lenALL} 人,用时:{stopwatch.ElapsedMilliseconds} 毫秒.");
Console.WriteLine($"\r\n是否超售:{(lenALL > stockNumber ? "是" : "否")}");
Console.WriteLine("\r\n秒杀成功人员名单:");
for (int i = 0; i < stockNumber; i++)
{
Console.WriteLine(csredis.RPop(key));
}
}
3.结论
至此,结束。
更多推荐
所有评论(0)