首页 > 解决方案 > 如何证明在紧密循环中持有和延迟的锁可能无法被等待线程获取

问题描述

......但有一个奇怪的结果。

我正在重试线程上查看代码。这个想法很简单:查找缓存,如果它不在缓存中,则延迟并重试。另一个线程显然正在写入相同的缓存并使用相同的锁定对象。

我发现了我认为是一个潜在的错误,其中有一个 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 个测试用例,结果如下:

  1. 使用 Task.Delay(1).Wait() 锁定。
  2. 在锁定中使用 Thread.Sleep(1)
  3. 无延迟锁定

情况1:写线程更快地获取锁。立即,一旦写入线程退出其简单的计数器循环。

测试用例 1

在情况2和3中:这在一定程度上证明了写线程可能在一段时间内无法获取锁的理论。换句话说,它花了更长的时间。

测试用例 2

注意两个图像中的差距。我的问题是为什么案例 1 明显更快?或许我当时错了……

我了解此代码不是确定性的,甚至可能存在缺陷。对替代品持开放态度。

标签: c#.netmultithreadingtask

解决方案


推荐阅读