在Montor中使用复杂对象是否有任何副作用.输入lock

本文关键字:任何 副作用 输入 lock 是否 对象 Montor 复杂 | 更新日期: 2023-09-27 17:58:19

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

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

我猜是蒙托。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);
        }
    }
}

如果监视器。Enter只是使用一个指向内存中底层对象的引用指针,理论上它是一个大而复杂的对象,而不是一个微小的新对象(),这不会有任何副作用。

有人有这方面的数据吗?

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

  1. 底层对象周围的瘦包装允许创建用于锁定的私有只读对象
  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();
        }
    }
}

在Montor中使用复杂对象是否有任何副作用.输入lock

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

  1. 大多数时候应该只锁定只读引用
  2. 为您的锁创建一个新的专用object,以清楚起见,并且因为有人可能正在锁定自己,请参阅此答案以获取更多信息
  3. 除非方法在程序级别锁定,否则不要将锁设为静态

您可以阅读更多关于lock关键字和Monitor的信息。在MSDN上输入:

  • 监视器。输入方法(对象)
  • https://msdn.microsoft.com/en-us/library/c5kehkcz.aspx

这很好。NET使用一点Object头来有效地创建和使用自旋锁,或者如果失败,它使用一个信号池。

在任何一种情况下,它都基于所有对象所在的底层Object标头。NET有。包含的对象有多复杂或简单并不重要。

我的猜测是,Montor.Enter使用某种指向内存中对象的引用指针来构建由哪个线程锁定的内容的内部字典。但不确定这是否正确。

正如其他人所指出的,实际上在每一个.NET引用类型中都内置了一个Monitor。任何线程所持有的内容都没有一个真正的"字典"(或任何其他集合)。

我想知道在Monitor.Enter参数中使用更复杂的对象是否有任何后果。

使用任何引用类型都可以。然而

多个线程正试图广播到WebSocket

在这种情况下,优先选择排队。特别地,await不能存在于lock内部。通过使用异步兼容锁可以进行一种隐式排队,但这完全是另一回事。

此外,不建议锁定参数。如果这个例子是同步的,仍然不推荐使用:

// NOT recommended
public void SendMessage(WebSocket socket, ArraySegment<byte> data)
{
  lock (socket)
  ...
}

有一些锁定指南是多年来发展起来的:

  • 锁应该是私有的。由于任何代码都可以锁定,因此只要锁定了任何其他代码都可以访问的实例,就有可能出现死锁。请注意,隐私在该规则中很重要,因此lock(this)通常被理解为"不推荐",但原因不是因为您"不应该锁定this",而是因为"this不是私有的,您应该只锁定私有实例"
  • 永远不要在持有锁的情况下调用任意代码。这包括引发事件或调用回调。这再次开启了僵局的可能性
  • lock中的代码(在"关键部分"中)应尽可能短
  • 为了代码的可读性和可维护性,通常最好有一个显式的"互斥"对象(即_sync
  • 应该记录"互斥锁"保护的其他对象
  • 避免使用需要获取多个锁的代码。如果这是不可避免的,请建立并记录锁层次结构,以便始终以相同的顺序获取锁

这些规则自然产生了常见的互斥代码:

private readonly object _sync = new object();