Skip to main content
 首页 » 编程设计

c#之在 Monitor.Enter 锁中使用复杂对象是否有任何副作用

2024年01月26日28findumars

我见过的大多数锁定代码示例都使用这样的模式:

private static int _counter = 0; 
 
private static readonly object _sync = new object(); 
 
public void DoWork() 
{ 
    int counter; 
    lock (_sync) 
    { 
        counter = _counter++; 
    } 
    // etc ... 
} 

我的猜测是 Montor.Enter 使用某种指向存在于内存中的对象的引用指针来构建一些关于什么被什么线程锁定的内部字典。但是,不确定这是否正确。

我想知道在 Monitor.Enter 参数中使用更复杂的对象是否有任何后果。例如,如果多个线程试图向 WebSocket 广播,则有必要要么

  1. 将请求排队并让一个线程负责发送,或者
  2. 使用锁定来防止多个线程发送到同一个套接字。

假设 WebSocket 对象本身用于锁定:

public async Task SendMessage(WebSocket socket, ArraySegment<byte> data) 
{ 
    lock (socket) 
    { 
        if (socket.State == WebSocketState.Open) 
        { 
            await socket.SendAsync( 
                data, 
                WebSocketMessageType.Text, 
                true, 
                CancellationToken.None); 
        } 
    } 
} 

如果 Monitor.Enter 只是简单地使用一个指向内存中底层对象的引用指针,理论上不会有副作用,因为它是一个大而复杂的对象,而不是一个很小的新对象 ()。

有没有人有这方面的资料?

编辑:在下面的一些答案之后,我想出了一个替代模式,扩展了 WebSocket 示例。我们将不胜感激任何进一步的反馈。

  1. 底层对象的薄包装允许创建私有(private)只读对象以用于锁定。
  2. 锁内的异步方法是同步的。

请注意,此模式没有考虑仅允许单个线程访问 WebSocket 连接(通过队列系统)的建议——我主要是尝试通过我对锁定模式的理解来工作举个具体的例子。

public class SocketWrapper 
{ 
    private readonly object _sync = new object(); 
 
    public WebSocket Socket { get; private set; } 
 
    public SocketWrapper(WebSocket socket) 
    { 
        this.Socket = socket; 
    } 
 
    public async Task SendMessage(ArraySegment<byte> data) 
    { 
        await Task.Yield(); 
 
        lock (this._sync) 
        { 
            var t = await this.Socket.SendAsync( 
                data, 
                WebSocketMessageType.Text, 
                true, 
                CancellationToken.None); 
            t.Wait(); 
        } 
    } 
} 

请您参考如下方法:

锁定机制使用对象的 header 来锁定,对象有多复杂并不重要,因为 header 就是该机制所使用的。然而,这是锁的一个很好的经验法则。

  1. 大多数时候应该只锁定只读引用
  2. 为您的锁创建一个新的私有(private)对象 以确保清晰明了,因为有人可能会锁定自己 see this answer了解更多信息
  3. 除非您的方法在程序级别锁定,否则不要将您的锁设为静态

您可以在 MSDN 上阅读有关 lock 关键字和 Monitor.Enter 的更多信息: