【问题标题】:Socket Locking across Threads跨线程的套接字锁定
【发布时间】:2011-05-30 19:29:59
【问题描述】:

在我的异步套接字的 ReceiveCallBack 中锁定()那里的套接字是个好主意吗?我问是因为有可能另一个线程同时在套接字上发送数据。

private void ReceiveCallback(IAsyncResult ar)
{
StateObject state = (StateObject)ar.AsyncState;
Socket client = state.workSocket;

lock(client)
{
    int bytesRead = client.EndReceive(ar);
    // do some work

    // Kick off socket to receive async again.
    client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
            new AsyncCallback(ReceiveCallback), state);
}
}    

// This is commonly called by another thread
public void SendMessage(string cmdName, Object data)
{
    lock (client)
    {
         client.Send(arrayofdata, 0, arraylength, 0);
    }
}

【问题讨论】:

  • 这个问题是基于您可以锁定对象以使代码线程安全的错觉。你不能,你只能阻止代码,阻止它同时使用共享对象。 lock 语句仅使用一个对象来存储状态。那应该永远是一个套接字。

标签: c# .net multithreading sockets parallel-processing


【解决方案1】:

如果你想让它线程安全并且能够同时发送和接收,你需要创建两个锁同步对象:

private readonly object sendSyncRoot = new object();
private readonly object receiveSyncRoot = new object();

private void ReceiveCallback(IAsyncResult ar)
{
    StateObject state = (StateObject)ar.AsyncState;
    Socket client = state.workSocket;

    lock (receiveSyncRoot)
    {
        int bytesRead = client.EndReceive(ar);
        // do some work

        // Kick off socket to receive async again.
        client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                new AsyncCallback(ReceiveCallback), state);
    }
}

// This is commonly called by another thread
public void SendMessage(string cmdName, Object data)
{
    lock (sendSyncRoot)
        client.Send(arrayofdata, 0, arraylength, 0);
}

拥有专用的syncRoot 对象而不是锁定其他类或成员通常是一个好习惯。这样可以避免微妙的死锁。

【讨论】:

  • 锁定套接字本身以外的某些对象肯定更好。
  • 嗯,当从一个线程读取并从另一个线程写入时,我从未锁定任何语言的任何套接字。它在 C++、Delphi 中工作得很好。为什么 C# 不同,除了被管理?为什么 .NET 对非托管代码中不需要显式锁定的操作强制显式锁定?如果两个线程可以写,那很好,做一些锁定,但是一个写一个读?
  • @Martin James - 这取决于。如果您希望读取和写入同时发生,则根本不需要锁定。但是如果你想要两个读取或两个写入同时发生,你需要锁定。我不知道提供的代码只是一个示例还是真实的,但是如果真实的代码是在不止一个发送中写入数据,则您需要保证没有其他线程干扰。
  • @Martin James - 假设 SendMessage 方法发送文件。所以里面的 SendMessage 将从文件流中读取并通过套接字发送。现在假设两个线程开始发送。不加锁,两个文件的内容会混在一起。
  • 是的 - 如果可以有多个作者,您需要在某处进行一些锁定。多个读者似乎没有多大意义 - 无法想象任何人尝试它的理由!
【解决方案2】:

没有。不要那样做。处理套接字的最佳方法是封装。除了声明它的类之外,不要将它暴露给任何东西。通过这样做,很容易确保一次只有一个接收处于待处理状态。无需为它使用锁。

至于发送。做这样的事情:

public class MyClient
{
    private readonly Queue<byte[]> _sendBuffers = new Queue<byte[]>();
    private bool _sending;
    private Socket _socket;

    public void Send(string cmdName, object data)
    {
        lock (_sendBuffers)
        {
            _sendBuffers.Enqueue(serializedCommand);
            if (_sending) 
                return;

            _sending = true;
            ThreadPool.QueueUserWorkItem(SendFirstBuffer);
        }
    }

    private void SendFirstBuffer(object state)
    {
        while (true)
        {
            byte[] buffer;
            lock (_sendBuffers)
            {
                if (_sendBuffers.Count == 0)
                {
                    _sending = false;
                    return;
                }

                buffer = _sendBuffers.Dequeue();
            }

            _socket.Send(buffer);
        }
    }
}

这种方法不会阻塞任何调用者,所有发送请求都会依次处理。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-03-18
    • 2019-11-19
    • 2011-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多