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

时间:2010-02-02 19:35:47

标签: c# concurrency

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

  1. 两个或多个线程需要同时加载一组给定的值
  2. 每个值只能执行一个请求,因此如果两个线程需要加载相同的子集,则必须等待另一个
  3. 我不希望每个值都有锁(或互斥或其他原语),因为它们可能太高了。
  4. 场景可能是(假设线程B稍早进入)

              thread A           thread B
    values    5, 8, 9, 12        7, 8, 9, 13, 14
    request   5,       12        7, 8, 9, 13, 14
    waits for    8, 9
                                 >>data loaded<<
    retrieves    8, 9
    
              >> data loaded <<
    returns   5, 8, 9, 12
    

    我应该使用哪种并发机制?

    请记住,生产者/消费者不会工作,因为线程A和B不完全是消费者(他们只对某些数据感兴趣)。

    由于

1 个答案:

答案 0 :(得分:1)

这听起来很像一个锁定管理器,为什么不建立一个呢?

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();
        }
    }
}

您必须在使用之前锁定每个值:

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();
        }
    }
}

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