两个线程一个核心

时间:2014-01-13 15:32:43

标签: c# multithreading autoresetevent

我正在玩一个创建一个线程的简单控制台应用程序,我在主线程和工作线程之间进行一些线程间的通信。

我将主线程中的对象发布到并发队列,而工作线程将该队列出列并进行一些处理。

令我感到奇怪的是,当我描述这个应用程序时,即使我有两个核心。 一个核心100%免费,另一个核心完成所有工作,我看到两个线程都在该核心中运行。 enter image description here

为什么会这样?

是因为我在发布消息时使用等待句柄,在处理完成后释放吗?

这是我的示例代码,现在使用2个工作线程。 它仍然表现相同,main,worker1和worker2在同一个核心中运行。 想法?

[编辑] 它现在有点起作用,至少,与昨天相比,我获得了两倍的性能。 诀窍是减慢消费者的速度,以避免使用AutoResetEvent发出信号。

public class SingleThreadDispatcher
{
    public long Count;
    private readonly ConcurrentQueue<Action> _queue = new ConcurrentQueue<Action>();
    private volatile bool _hasMoreTasks;
    private volatile bool _running = true;
    private int _status;
    private readonly AutoResetEvent _signal = new AutoResetEvent(false);
    public SingleThreadDispatcher()
    {
        var thread = new Thread(Run)
        {
            IsBackground = true,
            Name = "worker" + Guid.NewGuid(),           
        };

        thread.Start();
    }

    private void Run()
    {
        while (_running)
        {

            _signal.WaitOne();
            do
            {
                _hasMoreTasks = false;

                Action task;
                while (_queue.TryDequeue(out task) && _running)
                {
                    Count ++;
                    task();
                }
                //wait a short while to let _hasMoreTasks to maybe be set to true
                //this avoids the roundtrip to the AutoResetEvent
                //that is, if there is intense pressure on the pool, we let some new
                //tasks have the chance to arrive and be processed w/o signaling
                if(!_hasMoreTasks)
                    Thread.Sleep(5);

                Interlocked.Exchange(ref _status, 0);
            } while (_hasMoreTasks);
        }
    }

    public void Schedule(Action task)
    {
        _hasMoreTasks = true;
        _queue.Enqueue(task);

        SetSignal();
    }

    private void SetSignal()
    {
        if (Interlocked.Exchange(ref _status, 1) == 0)
        {
            _signal.Set();
        }
    }
}

4 个答案:

答案 0 :(得分:7)

  

是因为我在发布消息时使用等待句柄,在处理完成后释放吗?

如果没有看到你的代码,很难肯定地说,但是根据你的描述,你写的两个线程似乎充当了协同例程:当主线程运行时,工作线程没有任何关系,并且反之亦然。看起来.NET调度程序非常智能,在发生这种情况时不会加载第二个核心。

您可以通过多种方式更改此行为 - 例如

  • 在等待句柄之前在主线程上做一些工作,或者
  • 通过添加更多工作线程来竞争主线程发布的任务,并且可以同时完成任务。

答案 1 :(得分:2)

好的,我已经弄明白问题是什么了。 在这种情况下,生产者和消费者几乎一样快。 这导致消费者快速完成所有工作,然后循环返回以等待AutoResetEvent。 生成器下次发送任务时,必须触摸AutoresetEvent并进行设置。

解决方案是在消费者中添加非常小的延迟,使其比生产者稍慢。 这导致生产者发送任务时,它注意到消费者已经处于活动状态,并且只需要发送到工作队列而不接触AutoResetEvent。

原始行为导致了一种乒乓效应,可以在屏幕截图中看到。

答案 2 :(得分:1)

Dasblinkelight(可能)有正确答案。

除此之外,当你的一个线程被I / O绑定时(也就是说,它没有卡在CPU上),它也是正确的行为 - 在这种情况下,你已经使用多个内核没有任何好处,.NET足够聪明,可以在一个内核上更改上下文。

这通常是UI线程的情况 - 它几乎没有什么工作要做,所以通常没有太多理由让它为自己占用整个核心。是的,如果您的并发队列没有正确使用,它可能只是意味着主线程等待工作线程 - 再次,在这种情况下,没有必要切换核心,因为原始线程无论如何都在等待。

答案 3 :(得分:1)

您应该使用BlockingCollection而不是ConcurrentQueue。默认情况下,BlockingCollection使用了ConcurrentQueue,但它有一个更容易使用的界面。特别是,它会进行非繁忙的等待。此外,BlockingCollection支持cancellation,因此您的消费者变得非常简单。这是一个例子:

public class SingleThreadDispatcher
{
    public long Count;
    private readonly BlockingCollection<Action> _queue = new BlockingCollection<Action>();
    private readonly CancellationTokenSource _cancellation = new CancellationTokenSource();

    public SingleThreadDispatcher()
    {
        var thread = new Thread(Run)
        {
            IsBackground = true,
            Name = "worker" + Guid.NewGuid(),
        };

        thread.Start();
    }

    private void Run()
    {
        foreach (var task in _queue.GetConsumingEnumerable(_cancellation.Token))
        {
            Count++;
            task();
        }
    }

    public void Schedule(Action task)
    {
        _queue.Add(task);
    }
}

GetConsumingEnumerable的循环将在队列上执行非繁忙等待。没有必要单独举办活动。它将等待一个项目添加到队列中,或者如果您设置了取消令牌,它将退出。

要正常停止,只需拨打_queue.CompleteAdding()即可。这告诉消费者不再有任何项目添加到队列中。消费者将清空队列然后退出。

如果您想提前退出,请拨打_cancellation.Cancel()。这将导致GetConsumingEnumerable退出。

一般情况下,您不应该直接使用ConcurrentQueueBlockingCollection更易于使用,并提供相同的性能。

相关问题