等待给定结果的C#多线程并发机制

等待给定结果的C#多线程并发机制,c#,concurrency,C#,Concurrency,我需要一种机制来实现以下场景: 两个或多个线程需要同时加载一组给定的值 每个值只能执行一个请求,因此如果两个线程需要加载相同的子集,那么一个线程必须等待另一个线程 我不想在每个值上都有一个锁(或互斥锁,或另一个原语),因为它们可能太高了 场景可能是(假设线程B稍微提前一点进入) 线程A线程B 值5,8,9,12,7,8,9,13,14 请求5、12、7、8、9、13、14 等待8点,9点 >>数据加载>数据加载这听起来很像锁管理器,为什么不构建一个锁管理器呢 class LockManager&

我需要一种机制来实现以下场景:

  • 两个或多个线程需要同时加载一组给定的值
  • 每个值只能执行一个请求,因此如果两个线程需要加载相同的子集,那么一个线程必须等待另一个线程
  • 我不想在每个值上都有一个锁(或互斥锁,或另一个原语),因为它们可能太高了
  • 场景可能是(假设线程B稍微提前一点进入)

    线程A线程B
    值5,8,9,12,7,8,9,13,14
    请求5、12、7、8、9、13、14
    等待8点,9点
    
    >>数据加载>数据加载这听起来很像锁管理器,为什么不构建一个锁管理器呢

    class LockManager<TKey>
    {
        private Dictionary<TKey, List<EventWaitHandle>> locks =
            new Dictionary<TKey, List<EventWaitHandle>>();
        private Object syncRoot = new Object();
    
        public void Lock(TKey key)
        {
            do
            {
                Monitor.Enter(syncRoot);
                List<EventWaitHandle> waiters = null;
                if (true == locks.TryGetValue(key, out waiters))
                {
                    // Key is locked, add ourself to waiting list
                    // Not that this code is not safe under OOM conditions
                    AutoResetEvent eventLockFree = new AutoResetEvent(false);
                    waiters.Add(eventLockFree);
                    Monitor.Exit(syncRoot);
                    // Now wait for a notification
                    eventLockFree.WaitOne();
                }
                else
                {
                    // event is free
                    waiters = new List<EventWaitHandle>();
                    locks.Add(key, waiters);
                    Monitor.Exit(syncRoot);
                    // we're done
                    break;
                }
            } while (true);
    
        }
    
        public void Release(TKey key)
        {
            List<EventWaitHandle> waiters = null;
            lock (syncRoot)
            {
                if (false == locks.TryGetValue(key, out waiters))
                {
                    Debug.Assert(false, "Releasing a bad lock!");
                }
                locks.Remove(key);
            }
            // Notify ALL waiters. Unfair notifications
            // are better than FIFO for reasons of lock convoys
            foreach (EventWaitHandle waitHandle in waiters)
            {
                waitHandle.Set();
            }
        }
    }
    
    类锁管理器
    {
    专用字典锁=
    新字典();
    私有对象syncRoot=新对象();
    公用无效锁(TKey key)
    {
    做
    {
    Monitor.Enter(syncRoot);
    列表等待者=null;
    if(true==locks.TryGetValue(key,out waiters))
    {
    //钥匙已锁定,请将自己添加到等待列表中
    //并不是说该代码在OOM条件下不安全
    AutoResetEvent eventLockFree=新的AutoResetEvent(假);
    waiters.Add(eventLockFree);
    Monitor.Exit(syncRoot);
    //现在等待通知
    eventLockFree.WaitOne();
    }
    其他的
    {
    //活动是免费的
    waiters=新列表();
    锁。添加(钥匙、服务员);
    Monitor.Exit(syncRoot);
    //我们结束了
    打破
    }
    }虽然(正确);
    }
    公开无效释放(TKey)
    {
    列表等待者=null;
    锁定(同步根)
    {
    if(false==locks.TryGetValue(key,out waiters))
    {
    Assert(false,“释放坏锁!”);
    }
    锁。取下(钥匙);
    }
    //通知所有服务员。不公平的通知
    //由于锁定车队的原因,其性能优于先进先出(FIFO)
    foreach(waiters中的EventWaitHandle waitHandle)
    {
    waitHandle.Set();
    }
    }
    }
    
    必须先锁定每个值,然后才能使用它:

    class Program
    {
        class ThreadData
        {
            public LockManager<int> LockManager { get; set; }
            public int[] Work { get; set; }
            public AutoResetEvent Done { get; set; }
        }
    
        static void Main(string[] args)
        {
            int[] forA = new int[] {5, 8, 9, 12};
            int[] forB = new int[] {7, 8, 9, 13, 14 };
    
            LockManager<int> lockManager = new LockManager<int>();
    
            ThreadData tdA = new ThreadData
            {
                LockManager = lockManager,
                Work = forA,
                Done = new AutoResetEvent(false),
            };
            ThreadData tdB = new ThreadData
            {
                LockManager = lockManager,
                Work = forB,
                Done = new AutoResetEvent(false),
            };
    
            ThreadPool.QueueUserWorkItem(new WaitCallback(Worker), tdA);
            ThreadPool.QueueUserWorkItem(new WaitCallback(Worker), tdB);
    
            WaitHandle.WaitAll(new WaitHandle[] { tdA.Done, tdB.Done });
        }
    
        static void Worker(object args)
        {
            Debug.Assert(args is ThreadData);
            ThreadData data = (ThreadData) args;
            try
            {
                foreach (int key in data.Work)
                {
                    data.LockManager.Lock(key);
                    Console.WriteLine("~{0}: {1}",
                        Thread.CurrentThread.ManagedThreadId, key);
                    // simulate the load the set for Key
                    Thread.Sleep(1000);
                }
                foreach (int key in data.Work)
                {
                    // Now free they locked keys
                    data.LockManager.Release(key);
                }
            }
            catch (Exception e)
            {
                Debug.Write(e);
            }
            finally
            {
                data.Done.Set();
            }
        }
    }
    
    类程序
    {
    类线程数据
    {
    public LockManager LockManager{get;set;}
    公共int[]工作{get;set;}
    public AutoResetEvent Done{get;set;}
    }
    静态void Main(字符串[]参数)
    {
    int[]forA=新的int[]{5,8,9,12};
    int[]forB=新的int[]{7,8,9,13,14};
    LockManager LockManager=新的LockManager();
    ThreadData tdA=新的ThreadData
    {
    LockManager=LockManager,
    工作=论坛,
    完成=新的自动重置事件(错误),
    };
    ThreadData tdB=新的ThreadData
    {
    LockManager=LockManager,
    工作=forB,
    完成=新的自动重置事件(错误),
    };
    QueueUserWorkItem(新WaitCallback(Worker),tdA);
    QueueUserWorkItem(新WaitCallback(Worker),tdB);
    WaitHandle.WaitAll(新的WaitHandle[]{tdA.Done,tdB.Done});
    }
    静态无效辅助对象(对象参数)
    {
    Assert(args是ThreadData);
    ThreadData数据=(ThreadData)参数;
    尝试
    {
    foreach(data.Work中的int键)
    {
    data.LockManager.Lock(钥匙);
    Console.WriteLine(“{0}:{1}”,
    Thread.CurrentThread.ManagedThreadId,键);
    //模拟为关键点设置的负载
    睡眠(1000);
    }
    foreach(data.Work中的int键)
    {
    //现在他们把钥匙锁上了
    数据.锁管理器.释放(钥匙);
    }
    }
    捕获(例外e)
    {
    Debug.Write(e);
    }
    最后
    {
    data.Done.Set();
    }
    }
    }
    

    你将面临的最大问题是僵局。将这两个数组更改为{5,8,9,7}和{7,8,9,5},您将立即看到我的观点。

    这些值是不可变的吗?加载一个值需要多长时间?您是说您有一个数据流,该数据流中的每个项目都需要由两个线程处理吗?延迟是一个大问题吗?每个请求都会有自己的编号,大多数情况下它们会被缓存,然后不会被请求。请求是昂贵的,这就是为什么我们只想在加载一次值后执行此操作,请查看任务队列。除非我没有抓住要点,否则您正在创建一个锁列表,每个项一个锁,这正是我想要避免的,因为这会对系统造成过度伤害,对吗?每个服务员一个锁,用于锁定的项。服务生(=线程)一次只能等待一个项目,因此最坏的情况下,您的事件数与线程数一样多(更少,精确匹配意味着您处于死锁状态,但死锁是您需求中固有的,只有通过正确分配集合才能避免),为了避免死锁,策略是:你去交付那些不在等待名单上的,然后等待那些你丢失的,这样可以避免死锁,不是吗?我考虑的另一个选择是:你只需要使用监视器,输入排除,检查“等待名单”上的内容然后从请求中删除已经存在的内容,然后继续,获取剩余元素并等待监视器。每次有人发送东西时,会唤醒所有等待的线程,他们会检查是否有东西给他们,否则(或者直到他们的列表完成)他们会再次睡觉。它的效率不高,因为线程可以被唤醒而什么也不做,但它只使用一个监视器来监视所有事情。你认为呢?这是相似的,不同的是有一个地球仪
    class Program
    {
        class ThreadData
        {
            public LockManager<int> LockManager { get; set; }
            public int[] Work { get; set; }
            public AutoResetEvent Done { get; set; }
        }
    
        static void Main(string[] args)
        {
            int[] forA = new int[] {5, 8, 9, 12};
            int[] forB = new int[] {7, 8, 9, 13, 14 };
    
            LockManager<int> lockManager = new LockManager<int>();
    
            ThreadData tdA = new ThreadData
            {
                LockManager = lockManager,
                Work = forA,
                Done = new AutoResetEvent(false),
            };
            ThreadData tdB = new ThreadData
            {
                LockManager = lockManager,
                Work = forB,
                Done = new AutoResetEvent(false),
            };
    
            ThreadPool.QueueUserWorkItem(new WaitCallback(Worker), tdA);
            ThreadPool.QueueUserWorkItem(new WaitCallback(Worker), tdB);
    
            WaitHandle.WaitAll(new WaitHandle[] { tdA.Done, tdB.Done });
        }
    
        static void Worker(object args)
        {
            Debug.Assert(args is ThreadData);
            ThreadData data = (ThreadData) args;
            try
            {
                foreach (int key in data.Work)
                {
                    data.LockManager.Lock(key);
                    Console.WriteLine("~{0}: {1}",
                        Thread.CurrentThread.ManagedThreadId, key);
                    // simulate the load the set for Key
                    Thread.Sleep(1000);
                }
                foreach (int key in data.Work)
                {
                    // Now free they locked keys
                    data.LockManager.Release(key);
                }
            }
            catch (Exception e)
            {
                Debug.Write(e);
            }
            finally
            {
                data.Done.Set();
            }
        }
    }