C#线程安全get / set

这是C#的一个细节问题。

假设我有一个对象的类,并且该对象受锁的保护:

Object mLock = new Object(); MyObject property; public MyObject MyProperty { get { return property; } set { property = value; } } 

我想要一个轮询线程能够查询该属性。 我还希望线程偶尔更新该对象的属性,有时用户可以更新该属性,并且用户希望能够看到该属性。

以下代码是否会正确locking数据?

 Object mLock = new Object(); MyObject property; public MyObject MyProperty { get { lock (mLock){ return property; } } set { lock (mLock){ property = value; } } } 

“正确”,我的意思是,如果我想打电话

 MyProperty.Field1 = 2; 

或者别的什么,当我更新的时候,这个字段会被locking吗? 在'get'函数的范围内是由等号运算符完成的设置,还是'get'函数(因此locking)首先完成,然后设置,然后'set'被调用,从而绕过锁?

编辑:由于这显然不会做的伎俩,会有什么? 我需要做些什么:

 Object mLock = new Object(); MyObject property; public MyObject MyProperty { get { MyObject tmp = null; lock (mLock){ tmp = property.Clone(); } return tmp; } set { lock (mLock){ property = value; } } } 

这或多或less只是确保我只能访问一个副本,这意味着如果我有两个线程同时调用“get”,它们每个都会以相同的Field1值开始(对吧?)。 有没有办法读取和写入locking属性是有道理的? 还是应该限制自己lockingfunction部分而不是数据本身?

只是为了让这个例子有意义:MyObject是一个asynchronous返回状态的设备驱动程序。 我通过串口发送命令,然后设备在自己的甜蜜时间响应这些命令。 现在,我有一个线程轮询它的状态(“你还在吗?你能接受命令吗?”),一个等待串口响应的线程(“刚刚得到状态string2,一切都很好” ),然后是接收其他命令的UI线程(“用户希望你做这个事情”),并发布驱动程序的响应(“我刚刚完成了这个事情,现在更新UI”)。 这就是为什么我要locking对象本身,而不是对象的字段; 这将是大量的锁,a和b,并不是这个类的每一个设备都有相同的行为,只是一般的行为,所以如果我个性化了锁,我将不得不编写大量的单个对话框。

不,您的代码不会locking从MyProperty返回的对象成员的访问权限。 它只lockingMyProperty本身。

您的示例用法实际上是将两个操作合并为一个,大致等同于:

 // object is locked and then immediately released in the MyProperty getter MyObject o = MyProperty; // this assignment isn't covered by a lock o.Field1 = 2; // the MyProperty setter is never even called in this example 

简而言之 – 如果两个线程同时访问MyProperty ,则getter将暂时阻塞第二个线程,直到它将对象返回到第一个线程, 但是它会将对象返回到第二个线程。 这两个线程都将拥有完整的解锁访问对象。

编辑回应在问题的进一步细节

我仍然不是100%确定你想要达到什么目的,但是如果你只是想对对象进行primefaces访问,那么你有没有对对象本身的调用代码锁?

 // quick and dirty example // there's almost certainly a better/cleaner way to do this lock (MyProperty) { // other threads can't lock the object while you're in here MyProperty.Field1 = 2; // do more stuff if you like, the object is all yours } // now the object is up-for-grabs again 

不理想,但只要所有对象的访问都包含在lock (MyProperty)部分中,那么这种方法将是线程安全的。

如果您的方法可行,并发编程将非常简单。 但是事实并非如此,那泰坦尼克号的冰山就是例如你们class的客户这样做的:

 objectRef.MyProperty += 1; 

阅读 – 修改 – 写作比赛非常明显,还有更糟的。 绝对没有什么可以使你的属性线程安全,除了使它不可变。 这是你的客户,需要处理头痛。 被迫把这种责任委托给一个最不可能正确的程序员是并发编程的致命弱点。

正如其他人指出的那样,一旦你从getter中返回对象,你将失去对谁访问对象的控制。 为了做你想做的事情,你需要在对象本身中放置一个锁。

也许我不明白整个画面,但根据你的描述,听起来你不一定需要为每个单独的字段locking。 如果你有一组字段只是通过getters和setter来读写,那么你可能会为这些字段locking一个锁。 这样做显然有可能会不必要地串行化线程的操作。 但是,根据你的描述,这听起来并不像你在积极地访问这个对象。

我也build议使用事件而不是使用线程轮询设备状态。 使用轮询机制,每当线程查询设备时,都会触发locking。 通过事件机制,一旦状态改变,对象就会通知任何监听者。 在这一点上,你的“轮询”线程(不再是轮询)将醒来,并获得新的地位。 这将会更有效率。

举个例子…

 public class Status { private int _code; private DateTime _lastUpdate; private object _sync = new object(); // single lock for both fields public int Code { get { lock (_sync) { return _code; } } set { lock (_sync) { _code = value; } // Notify listeners EventHandler handler = Changed; if (handler != null) { handler(this, null); } } } public DateTime LastUpdate { get { lock (_sync) { return _lastUpdate; } } set { lock (_sync) { _lastUpdate = value; } } } public event EventHandler Changed; } 

你的“轮询”线程看起来像这样。

 Status status = new Status(); ManualResetEvent changedEvent = new ManualResetEvent(false); Thread thread = new Thread( delegate() { status.Changed += delegate { changedEvent.Set(); }; while (true) { changedEvent.WaitOne(Timeout.Infinite); int code = status.Code; DateTime lastUpdate = status.LastUpdate; changedEvent.Reset(); } } ); thread.Start(); 

在你的例子中的locking范围是不正确的地方 – 它需要在“MyObject”类的属性的范围,而不是它的容器。

如果我的MyObject对象类只是用来包含一个线程要写入的数据,而另一个(UI线程)从那里读取,你可能根本不需要一个setter,并构造一次。

还要考虑是否在属性级别lockinglocking粒度的写入级别; 如果为了表示交易状态而可能写入多个属性(例如:总订单和总重量),那么在MyObject级别locking(例如lock(myObject.SyncRoot))可能会更好。 。)

在你发布的代码示例中,get不会被执行。

在一个更复杂的例子中:

 MyProperty.Field1 = MyProperty.doSomething() + 2; 

当然假设你做了一个:

 lock (mLock) { // stuff... } 

doSomething()所有的锁调用都不足以保证整个对象的同步。 只要doSomething()函数返回,锁就会丢失,然后添加完成,然后分配发生,再次locking。

或者,以另一种方式写它,你可以假装像锁没有自动完成,并重写这个更像“机器代码”每行一个操作,它变得明显:

 lock (mLock) { val = doSomething() } val = val + 2 lock (mLock) { MyProperty.Field1 = val } 

multithreading的美妙之处在于你不知道将会发生什么事情。如果你在一个线程上设置了某个东西,它可能会首先发生,这可能发生在get之后。

您已经发布的代码在读取和写入时locking成员。 如果你想处理值更新的情况,也许你应该看看其他forms的同步,如事件 。 (检查自动/手动版本)。 然后你可以告诉你的“轮询”线程,这个值已经改变了,可以重新读取了。

在你编辑的版本中,你仍然没有提供线程安全的方式来更新MyObject。 对象属性的任何更改都需要在同步/locking块内完成。

你可以写个别的setter来处理这个问题,但是你已经指出由于大量的字段,这将是困难的。 如果确实如此(而且你还没有提供足够的信息来评估这个),另一种方法是编写一个使用reflection的二传手。 这将允许您传递一个string来表示字段名称,并且您可以dynamic查找字段名称并更新值。 这将允许你有一个可以在任意数量的字段上工作的setter。 这不是那么容易或有效,但它可以让你处理大量的类和领域。

你已经实现了获取/设置对象的锁,但你没有使对象线程安全,这是另一回事。

我写了一篇关于C#中不可变模型类的文章,在这种情况下可能很有趣: http : //rickyhelgesson.wordpress.com/2012/07/17/mutable-or-immutable-in-a-parallel-world/

C#锁是否不会像其他语言那样受到相同的locking问题?

例如

 var someObj = -1; // Thread 1 if (someObj = -1) lock(someObj) someObj = 42; // Thread 2 if (someObj = -1) lock(someObj) someObj = 24; 

这可能会导致两个线程最终获得锁并更改值的问题。 这可能会导致一些奇怪的错误。 但是,除非需要,否则不想不必要地locking对象。 在这种情况下,您应该考虑双重检查locking。

 // Threads 1 & 2 if (someObj = -1) lock(someObj) if(someObj = -1) someObj = {newValue}; 

只是要记住的东西。