c# - 如何证明在紧密循环中持有和延迟的锁可能无法被等待线程获取
问题描述
......但有一个奇怪的结果。
我正在重试线程上查看代码。这个想法很简单:查找缓存,如果它不在缓存中,则延迟并重试。另一个线程显然正在写入相同的缓存并使用相同的锁定对象。
我发现了我认为是一个潜在的错误,其中有一个 Task.Delay(1).Wait() 在重试线程的锁中,我的“论点”/理论是虽然重试循环释放了锁,但它仍然可以在写线程可以之前获取锁。
所以我决定看看我是否可以快速重现代码(相当糟糕):
namespace TaskDelay
{
class Program
{
static Dictionary<int, string> dict = new Dictionary<int, string>();
static BlockingCollection<int> bc = new BlockingCollection<int>();
static ManualResetEvent resetEvent = new ManualResetEvent(false);
static ManualResetEvent nresetEvent = new ManualResetEvent(false);
static void Main(string[] args)
{
new Program().Run();
Console.Read();
}
public void Run()
{
var t = new Thread(RetryThread);
t.Start();
var v = new Thread(TryAdd);
v.Start();
bc.Add(20);
bc.Add(21);
resetEvent.Set();
}
public void RetryThread()
{
resetEvent.WaitOne();
Console.WriteLine("Starting RetryLoop");
bool done = false;
nresetEvent.Set();
while (!done)
{
done = RetryLoop(21);
}
}
public bool RetryLoop(int x)
{
Console.WriteLine($"In Retry loop {x}");
for (int i = 0; i < 5; i++)
{
lock (dict)
{
Console.WriteLine($"Entering Retry loop {x}. Count {i+1}");
if (!dict.ContainsKey(x))
{
Task.Delay(1).Wait();//Change to Thread.Sleep(1)
continue;
}
Console.WriteLine($"eXITING Retry loop {x}");
return true;
}
}
return false;
}
void TryAdd()
{
Console.WriteLine("Starting TryAdd");
nresetEvent.WaitOne();
for (int i = 0; i < 2000; i++)
{
if (i == 1500)
break;
Thread.Sleep(1);
}
Console.BackgroundColor = ConsoleColor.Red;
Console.WriteLine("Exited loop");
Console.BackgroundColor = ConsoleColor.Black;
lock (dict)
{
Console.BackgroundColor = ConsoleColor.Red;
Console.WriteLine("Entered lock try add");
dict[21] = "hello";
Console.WriteLine("Exit lock try add");
Console.BackgroundColor = ConsoleColor.Black;
}
}
}
}
我在 RetryLoop 中尝试了 3 个测试用例,结果如下:
- 使用 Task.Delay(1).Wait() 锁定。
- 在锁定中使用 Thread.Sleep(1)
- 无延迟锁定
情况1:写线程更快地获取锁。立即,一旦写入线程退出其简单的计数器循环。
在情况2和3中:这在一定程度上证明了写线程可能在一段时间内无法获取锁的理论。换句话说,它花了更长的时间。
注意两个图像中的差距。我的问题是为什么案例 1 明显更快?或许我当时错了……
我了解此代码不是确定性的,甚至可能存在缺陷。对替代品持开放态度。
解决方案
推荐阅读
- javascript - Javascript - 通过减少和映射将对象转换为特定数组
- python - 在不同的 Python 文件中加载保存的 NN 模型
- reactjs - 在 React 中同步两个状态而没有无限循环
- linux - 使用 Docker 容器的外部配置文件 - .NET 5.0
- javascript - 考虑组集合查询的firestore数据结构比较
- html - Flex 容器在其项目最大宽度上增长
- node.js - node-fetch 和谷歌分析测量协议
- avro - Avro 模式演化测试和问题
- ffmpeg - ffmpeg 如何在实时点开始编码(不是从头开始)
- postgresql - PGSQL 似乎忽略了传入的参数