多个生产者/单个消费者,处理没有锁的异步?

时间:2012-12-03 12:15:22

标签: c# multithreading

我得到了以下代码(在多线程环境中效果不佳)

public class SomeClass
{
    private readonly ConcurrentQueue<ISocketWriterJob> _writeQueue = new ConcurrentQueue<ISocketWriterJob>();
    private ISocketWriterJob _currentJob;

    public void Send(ISocketWriterJob job)
    {
        if (_currentJob != null)
        {
            _writeQueue.Enqueue(job);
            return;
        }

        _currentJob = job;
        _currentJob.Write(_writeArgs);

        // The job is invoked asynchronously here
    }

    private void HandleWriteCompleted(SocketError error, int bytesTransferred)
    {
        // error checks etc removed for this sample.

        if (_currentJob.WriteCompleted(bytesTransferred))
        {
            _currentJob.Dispose();
            if (!_writeQueue.TryDequeue(out _currentJob))
            {
                _currentJob = null;
                return;
            }
        }

        _currentJob.Write(_writeArgs);

        // the job is invoked asycnhronously here.
    }
}

如果没有正在执行的当前作业,Send方法应异步调用作业。如果存在,它应该排队。

锁定_currentJob任务/检查会使一切正常。但是有解锁方法吗?

更新

我正在使用套接字,它是SendAsync方法来发送信息。这意味着在调用Send()方法时,我不知道是否有写入/作业挂起。

4 个答案:

答案 0 :(得分:4)

考虑使用CompareExchange关于预期状态转换的假设。不需要使用ConcurrentQueue,因为现在我们可以控制同步。

更新了以使用状态机
再次更新以删除不需要的Interlocked.Exchange(用于状态分配)。

public class SomeClass
{
    private readonly Queue<ISocketWriterJob> _writeQueue = new Queue<ISocketWriterJob>();
    private ISocketWriterJob _currentJob;
    private enum State { Idle, Active, Enqueue, Dequeue };
    private State _state;

    public void Send(ISocketWriterJob job)
    {
        bool spin = true;
        while(spin)
        {
            switch(_state)
            {
            case State.Idle:
                if (Interlocked.CompareExchange(ref _state, State.Active, State.Idle) == State.Idle)
                {
                    spin = false;
                }
                // else consider new state
                break;
            case State.Active:
                if (Interlocked.CompareExchange(ref _state, State.Enqueue, State.Active) == State.Active)
                {
                    _writeQueue.Enqueue(job);
                    _state = State.Active;
                    return;
                }
                // else consider new state
                break;
            case State.Enqueue:
            case State.Dequeue:
                // spin to wait for new state
                Thread.Yield();
                break;
            }
        }

        _currentJob = job;
        _currentJob.Write(_writeArgs);

        // The job is invoked asynchronously here
    }

    private void HandleWriteCompleted(SocketError error, int bytesTransferred)
    {
        // error checks etc removed for this sample.

        if (_currentJob.WriteCompleted(bytesTransferred))
        {
            _currentJob.Dispose();

            bool spin = true;
            while(spin)
            {
                switch(_state)
                {
                case State.Active:
                    if (Interlocked.CompareExchange(ref _state, State.Dequeue, State.Active) == State.Active)
                    {
                        if (!_writeQueue.TryDequeue(out _currentJob))
                        {
                            // handle in state _currentJob = null;
                            _state = State.Idle;
                            return;
                        }
                        else
                        {
                            _state = State.Active;
                        }
                    }
                    // else consider new state
                    break;

                case State.Enqueue:
                    // spin to wait for new state
                    Thread.Yield();
                    break;

                // impossible states
                case State.Idle:
                case State.Dequeue:
                    break;
                }
            }
        }

        _logger.Debug(_writeArgs.GetHashCode() + ": writing more ");
        _currentJob.Write(_writeArgs);

        // the job is invoked asycnhronously here.
    }
}

答案 1 :(得分:1)

目前,您的生产者和消费者之间的分歧有点模糊;你有“将一份工作产生到一个队列或立即消费”和“从队列中消耗一份工作或者如果没有一份就退出”;它会更清晰,因为“在队列中生成一个作业”和“从队列中消耗一个作业(最初)”和“从队列中消耗一个作业(一旦作业完成”)。

这里的诀窍是使用BlockingCollection,这样您就可以等待来显示作业:

BlockingCollection<ISocketWriterJob> _writeQueue =
         new BlockingCollection<ISocketWriterJob>();

让调用Send的线程只是将作业排队:

public void Send(ISocketWriterJob job)
{
    _writeQueue.Add(job);
}

然后让另一个线程只消耗工作。

public void StartConsumingJobs()
{
    // Get the first job or wait for one to be queued.
    _currentJob = _writeQueue.Take();

    // Start job
}

private void HandleWriteCompleted(SocketError error, int bytesTransferred)
{
    if (_currentJob.WriteCompleted(bytesTransferred))
    {
        _currentJob.Dispose();

        // Get next job, or wait for one to be queued.
        _currentJob = _writeQueue.Take();
    }

    _currentJob.Write(_writeArgs);

   // Start/continue job as before
}

答案 2 :(得分:0)

我不认为你会从使用无锁技术中获得一些东西。即使使用简单锁定,您也可以保持用户模式,因为Monitor.Enter / Monitor.Exit首先使用了旋转,只有当您在等待状态下等待更长时间时才会转换进入内核模式。

这意味着基于锁的技术将表现得与任何无锁技术一样好,因为您只能锁定将作业存储到队列中并从中取回它,但是您将拥有更加清晰和强大的功能每个开发人员都能理解的代码:

public class SomeClass
{
    // We don't have to use Concurrent collections
    private readonly Queue<ISocketWriterJob> _writeQueue = new Queue<ISocketWriterJob>();
    private readonly object _syncRoot = new object();
    private ISocketWriterJob _currentJob;

    public void Send(ISocketWriterJob job)
    {
        lock(_syncRoot)
        {
            if (_currentJob != null)
            {
                _writeQueue.Enqueue(job);
                return;
            }
            _currentJob = job;
        }

        // Use job instead of shared state
        StartJob(job);
    }

    private void StartJob(ISocketWriterJob job)
    {
       job.Write(_writeArgs);
       // The job is invoked asynchronously here
    }

    private void HandleWriteCompleted(SocketError error, int bytesTransferred)
    {
        ISocketWriterJob currentJob = null;

        // error checks etc removed for this sample.
        lock(_syncRoot)
        {
           // I suppose this operation pretty fast as well as Dispose
           if (_currentJob.WriteCompleted(bytesTransferred))
            {
               _currentJob.Dispose();
              // There is no TryDequeue method in Queue<T>
              // But we can easily add it using extension method
              if (!_writeQueue.TryDequeue(out _currentJob))
              {
                  // We don't have set _currentJob to null
                  // because we'll achieve it via out parameter
                  // _currentJob = null;
                  return;
              }
           }

           // Storing current job for further work
           currentJob = _currentJob;
        }

        StartJob(currentJob);
    }
}

无锁是一种优化,就像任何其他优化一样,您应首先测量性能,以确保您的简单基于锁的实现存在问题,并且只有在其真实时才使用 - 使用一些较低级别的技术,如无锁。性能和可维护性是一种经典的权衡,您应该非常谨慎地选择。

答案 3 :(得分:-2)

您可以将当前作业标记为volatile,以确保当前线程获得最新状态。一般来说,锁定是有利的。

private volatile ISocketWriterJob _currentJob;