锁定属性,是一个好的方法吗?

14

在我的多线程应用程序中,我使用一些可以被多个实例同时修改的变量。虽然很奇怪但是它一直运行得很好,没有任何问题...但是当然我需要使其线程安全。我刚开始学习锁,所以希望您的建议:

当客户端连接时,会创建一个名为Client的类,每个Client都有自己的"A"变量。

有时候,Client会调用如下的方法:

Client selectedClient SelectOtherClientClassByID(sentID);

selectedClient.A=5;

目前即使有5个类同时执行(线程池),也没有出现问题,但我在思考是否需要为A属性添加锁定?

例如:

A {
    get { return mA; }
    set {
        // use lock here for settting A to some value
    }    
}

这样可以吗?

3个回答

19

你需要在获取和设置数据时都使用锁。这个锁必须是相同的对象。例如:

private object mylock = new object();

public int A {

  get {
    int result;
    lock(mylock) {
    result = mA; 
    }
    return result;
  } 

  set { 
     lock(mylock) { 
        mA = value; 
     }
  }
}

1
谢谢阅读,顺便问一下,这是因为在其他类的阅读过程中可能会发生更改吗? - Petr
2
不建议在此上加锁,因为它可以从类型之外访问。 - Brian Rasmussen
在你设置的时候它可能会发生变化。锁起到屏障的作用,直到锁的所有者释放锁之前,防止任何其他操作。 - NT_
@Brian,我同意。已经在我的代码上进行了更改。只是简单地演示而已。 - NT_
抱歉,我在开会。_NT,请问你修改代码之前的样子是怎样的?我的意思是,Brian提到了什么问题?我想尽可能多地学习 :) - Petr
我使用 lock(this) 锁定当前对象。 - NT_

6

在访问器中锁定属性的访问可能会导致错误的结果。例如,看下面的代码:

class C {
    private object mylock = new object();

    public int A {

      get {
        int result;
        lock(mylock) {
        result = mA; 
        }
        return result;
      } 

      set { 
         lock(mylock) { 
            mA = value; 
         }
      }
    }
}
C obj = new C;
C.A++;

这里存在一个竞态条件! "C.A++" 操作实际上需要两个单独的访问 A,一个是获取值,另一个是设置更新后的值。没有什么能确保这两个访问会在没有上下文切换的情况下一起执行。这是典型的竞态条件的场景!
因此,请注意!在访问器中放置锁不是一个好主意,应该像前面的答案建议的那样明确地获取锁(尽管不必使用 SyncRoots,任何对象都可以)。

你的意思是在代码中手动添加锁对象吗?我猜到目前为止我的代码中至少有20个读取和15个写入。 - Petr
为此,您需要扩展锁以覆盖整个关键区域。您不要执行A ++。 - NT_
NT:我可能没有理解你的意思。我有大约15个方法,每个方法都在不同的实例上使用该变量进行不同的操作。我不确定你所说的扩展锁是什么意思。 因此,如果我正确地阅读了以上内容,属性锁并不好用。 - Petr
@Petr:我的上一个评论是针对ilial的。至于你的评论,你需要确定这些是否有共同的同步对象。在这种情况下,你应该使用单个锁定。如果你对这一切都很新,请查看这个有趣的链接:http://www.codeproject.com/KB/threads/ThreadingDotNet3.aspx#CriticalSections - NT_

2

当你只需要设置单个属性时,这种情况非常少见。更常见的是,selectedClient.A = 5将成为一个更大的逻辑操作的一部分,其中涉及多个赋值/评估等操作。在整个操作期间,您更愿意让selectedClient处于一致的状态,而不是引入死锁/竞争条件。因此,在您的Client类中公开SyncRoot属性,并从调用代码上锁定该属性会更好:

Client selectedClient = GetClient(...);

lock(selectedClient.SyncRoot)
{
    selectedClient.A = 5;
    selectedClient.B = selectedClient.A * 54;
}

我没有其他共享变量,只有一个列表和一些整数。主要是,我还不了解SyncRoot :) - Petr
不建议使用SyncRoot:请参见http://blogs.msdn.com/brada/archive/2003/09/28/50391.aspx - NT_
@_NT:引用博客文章中的话:“请放心,我们在构建这些集合的通用版本时不会犯同样的错误。”现在,6年过去了,List<T>.SyncRoot在MSDN上没有任何令人沮丧的注释:http://msdn.microsoft.com/en-us/library/bb356596.aspx。 - Anton Gogolev
MSDN 远非正确编程的最佳资源!SyncRoot 是一个公共锁,也是一场灾难的前兆(根据 Jeffrey Richter 的《CLR via C#》)。 - NT_

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接