【问题标题】:Do I need to synchronize access the method arguments and locals when calling a method concurrently?并发调用方法时是否需要同步访问方法参数和本地变量?
【发布时间】:2014-07-20 16:38:12
【问题描述】:

我一直在编写大量带有静态类/方法的代码,我相信这些代码会被多个线程同时调用/执行。所以我在我的方法中做了很多锁定。我通常这样做:

public static class MyThreadsafeMethods {

    private static Object staticLock1 = new Object();
    private static Object staticLock2 = new Object();

    public static string StaticMethod1(string param1, int param2) {
        lock (staticLock1) {
            var _param1 = param1;
            var _param2 = param2;

            //highly confidential business logic here

            return StaticMethod2(_param1, "Integer: " + _param2.ToString());
        }
    }

    public static string StaticMethod2(string param1, string param2) {
        lock (staticLock2) {
            var _param1 = param1;
            var _param2 = param2;

            //truly groundbreaking algorithm here

            return _param1 + " - " + _param2;
        }
    }
}

我想知道两件事:

1) 我认为我需要在“锁定代码”中使用我的参数的本地“副本”;因为如果另一个线程使用不同的 param1 和 param2 值调用我的方法,那可能会打乱我的处理。如果我只使用在锁定代码中声明/实例化的变量(即上面示例中的 _param1 和 _param2),那么某些东西可能会更改 param1 和 param2 的值(或发送对不同对象的引用),我没问题。但我需要这样做吗?我是不是有不必要的偏执?

2) 我决定在需要它们之前不实例化我的锁定对象,因为我的静态锁定对象集合正在增长......所以,我现在开始这样做:

    private static Object staticLock1;

    public static string StaticMethod1(string param1, int param2) {
        lock(staticLock1 = staticLock1 ?? new Object()) {
            (...)
        }
    }

是否有任何理由在我第一次需要它时实例化我的锁对象是不安全的?在我的 lock 语句中对我的 lock 对象使用赋值运算符会以某种方式导致我出现问题或阻止对象被正确锁定吗?

【问题讨论】:

  • 你可以使用static readonly object
  • 我想我从来没有想过在我的锁对象上使用只读,因为我从来没有将它们重新分配给新的/不同的对象。但是使用它不会特别阻止我做我想做的事吗?我试图仅在锁定该对象的第一个静态方法中实例化锁定对象;并尽量减少要键入的步骤数/代码量。

标签: c# concurrency thread-safety locking


【解决方案1】:
  1. 你所拥有的根本不会有任何区别——参数本身在调用之后不能改变,所以它真的什么都不做。在您使用字符串的情况下,查看字符串如何不可变是完全安全的。如果不是这种情况,则传递的任何内容都可能在其他地方被更改。在这种情况下,您必须制作一个真实的副本(即不仅仅是复制参考),

  2. 考虑两个线程同时到达lock(staticLock1 = staticLock1 ?? new Object()) 的情况。他们都可以将staticLock1 视为空值。所以不,那不安全!

【讨论】:

  • 谢谢你,克里斯。我相信你完全回答了我的两个问题。我只想对一件事更加确定——考虑一下这种情况:线程 1 调用 StaticMethod1("teststring", 12)。因此,StaticMethod1 的“锁定”处理从 param1 = "teststring" 和 param2 = 12 开始。现在,线程 2 调用 StaticMethod1("newstring", 10)。我只是想确认在处理 StaticMethod1(由线程 1 调用)期间没有将 param1 设置为“newstring”。非常感谢您的信息,我只是想确保我正确理解了您的答案。
  • 线程有自己的上下文/状态。局部变量(参数是)对于每个线程都是完全独立的。当多个线程同时访问一些共享数据时,线程问题就会出现。
  • 正如其他人所提到的,从您在这里显示的代码来看,锁定是完全没有必要的。
【解决方案2】:

您的主要困惑似乎在于您需要什么同步才能安全地同时调用静态方法。

总是会出现数据竞争,因为多个线程以不同步的方式访问同一个存储位置,并且其中至少有一个是写入器。这个基本规则足以推理出很多并发问题。

调用方法时,每次调用的参数都有不同的存储位置。他们是独立的。因此,调用相同方法的两个线程永远不会竞相访问方法参数。因此,您永远不需要同步对方法参数的访问。

当地人也是如此。事实上,参数具有与本地相同的同步属性。它们存在于每个调用中。

回答您的第二个问题:这是不安全的,因为两个线程可能会锁定不同的对象。此外,您在多个线程上不同步地写入staticLock1。我已经解释过这是一场数据竞赛。

【讨论】:

    【解决方案3】:

    首先:创建 Object 实例的开销非常小。除非测量结果表明您应该担心,否则不要担心。

    像这样初始化:

    private readonly static object staticLock1 = new Object();
    

    您使用 lock 语句的方式安全。

    其次:我在方法中看不到共享数据,因此没有理由锁定。

    最后:如果它包含许多静态函数,我会重新考虑设计。

    【讨论】:

    • 很抱歉,我上面的示例代码可能与我的实际代码不完全匹配。我确信我确实需要锁定我的案子;我已经证明我的应用程序没有它会出现问题。你知道我提出的第一次需要它时实例化锁对象的代码是否会出现问题?第二种锁定 staticLock1 的方法是否比第一种方法更不安全?
    • 您在 lock 语句中的测试和分配方式是错误的。想象一下,如果在测试之后并且在获得锁之前另一个线程进行相同的测试会发生什么。除了这样做之外,每次锁定时都会执行此测试。
    猜你喜欢
    • 1970-01-01
    • 2016-09-21
    • 2020-10-10
    • 1970-01-01
    • 2016-05-14
    • 2015-06-09
    • 2021-11-21
    • 1970-01-01
    • 2011-10-30
    相关资源
    最近更新 更多