学习实现线程池 - 使用autoresetevent时丢失信号的事件

时间:2011-12-17 08:48:21

标签: .net multithreading thread-safety autoresetevent thread-synchronization

我坚信通过重塑来学习。凭借这种心态,我开始实现自定义线程池。我为自己设定的目标是:

  1. 能够在线程池上排队工作项。
  2. 能够处理具有固定线程数的工作项 - 所有这些都是同时创建的。
  3. 普通工作者线程函数应该只知道如何deque并且不应该处理其他函数/属性 IsEmpty或Count。
  4. 我成功实现了上述目标,但希望验证我在stackoverflow上与专家一起采取的方法。此外,想了解是否有更好的方法或多线程专家如何解决这个问题。以下段落提到了我所面临的挑战以及如何解决它。

    我创建的线程池在内部维护了一个工作项队列,所有工作线程都从中挑选了该项,然后对其进行处理。每当一个新项目排队时,它就会发出一个事件信号,这样任何一个免费线程都可以获取它并执行它。

    我开始使用autoresetevent来向队列中的任何新工作项发出等待线程的信号,但我遇到了丢失信号事件的问题。当多个项目排队并且没有可用于处理项目的空闲线程时,就会发生这种情况。未处理的总项目与由于重叠集(信令)事件而丢失的总信号事件相同。

    为了解决丢失信号事件的问题,我在autoresetevent之上创建了一个包装器,并用它来代替autoresetevent。它解决了这个问题。以下是相同的代码清单:

    public static class CustomThreadPool
    {
        static CustomThreadPool()
        {
            for (int i = 0; i < minThreads; i++)
                _threads.Add(
                    new Thread(ThreadFunc) { IsBackground = true }
                    );
    
            _threads.ForEach((t) => t.Start());
        }
    
        public static void EnqueWork(Action action)
        {
            _concurrentQueue.Enqueue(action);
            _enqueEvent.Set();
        }
    
        private static void ThreadFunc()
        {
            Action action = null;
            while (true)
            {
                _enqueEvent.WaitOne();
                _concurrentQueue.TryDequeue(out action);
                action();
            }
        }
    
        private static ConcurrentQueue<Action> _concurrentQueue = new ConcurrentQueue<Action>();
        private static List<Thread> _threads = new List<Thread>();
        private static CountAutoResentEvent _enqueEvent = new CountAutoResentEvent();
        private static object _syncObject = new object();
        private const int minThreads = 4;
        private const int maxThreads = 10;
    
        public static void Test()
        {
            CustomThreadPool.EnqueWork(() => {
    
                for (int i = 0; i < 10; i++) Console.WriteLine(i);
                Console.WriteLine("****First*****");
            });
    
            CustomThreadPool.EnqueWork(() =>
            {
                for (int i = 0; i < 10; i++) Console.WriteLine(i);
                Console.WriteLine("****Second*****");
            });
    
            CustomThreadPool.EnqueWork(() =>
            {
                for (int i = 0; i < 10; i++) Console.WriteLine(i);
                Console.WriteLine("****Third*****");
            });
    
            CustomThreadPool.EnqueWork(() =>
            {
                for (int i = 0; i < 10; i++) Console.WriteLine(i);
                Console.WriteLine("****Fourth*****");
            });
    
            CustomThreadPool.EnqueWork(() =>
            {
                for (int i = 0; i < 10; i++) Console.WriteLine(i);
                Console.WriteLine("****Fifth*****");
            });
        }
    }
    
    public class CountAutoResentEvent
    {
        public void Set()
        {
            _event.Set();
            lock (_sync)
                _countOfSet++;
        }
    
        public void WaitOne()
        {
            _event.WaitOne();
            lock (_sync)
            {
                _countOfSet--;
                if (_countOfSet > 0)
                    _event.Set();
            }
        }
    
        private AutoResetEvent _event = new AutoResetEvent(false);
        private int _countOfSet = 0;
        private object _sync = new object();
    }
    

    现在,我几乎没有问题:

    1. 我的方法是完全证明吗?
    2. 哪种同步机制最适合此问题?为什么?
    3. 多线程专家如何处理这个问题?
    4. 感谢。

1 个答案:

答案 0 :(得分:1)

从我所看到的,我说它是正确的。我喜欢你使用ConcurrentQueue并且没有实现自己的同步队列。这是一团糟,很可能不会像现有的那样快。

我只想指出,您的自定义“信号机制”实际上与信号量非常相似:一个允许多个线程进入临界区的锁。此功能已存在于Semaphore类中。