这是C#的详细问题.
假设我有一个带有对象的类,并且该对象受到锁的保护:
Object mLock = new Object(); MyObject property; public MyObject MyProperty { get { return property; } set { property = value; } }
我想要一个轮询线程来查询该属性.我还希望线程偶尔更新该对象的属性,有时用户可以更新该属性,并且用户希望能够看到该属性.
以下代码是否会正确锁定数据?
Object mLock = new Object(); MyObject property; public MyObject MyProperty { get { lock (mLock){ return property; } } set { lock (mLock){ property = value; } } }
通过'正确',我的意思是,如果我想打电话
MyProperty.Field1 = 2;
或者其他什么,我会在更新时锁定该字段?设置是由'get'函数范围内的equals运算符完成的,还是'get'函数(因此锁定)首先完成,然后设置,然后'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; } } }
这或多或少只是确保我只能访问一个副本,这意味着如果我有两个线程同时调用'get',它们每个都会以相同的Field1值开始(对吗?).有没有办法对有意义的属性进行读写锁定?或者我应该限制自己锁定函数的各个部分而不是数据本身?
只是为了让这个例子有意义:MyObject是一个异步返回状态的设备驱动程序.我通过串口发送命令,然后设备在自己的甜蜜时间响应这些命令.现在,我有一个线程轮询它的状态("你还在吗?你能接受命令吗?"),一个等待串口响应的线程("刚刚获得状态字符串2,一切都很好" ),然后是接受其他命令的UI线程("用户希望你做这件事.")并发布来自驱动程序的响应("我刚刚完成了这件事,现在用它来更新UI").这就是为什么我想要锁定对象本身,而不是对象的字段; 那将是大量的锁,a和b,并不是这个类的每个设备都有相同的行为,
不,您的代码不会锁定对从中返回的对象成员的访问权限MyProperty
.它只会锁定MyProperty
自己.
您的示例用法实际上是两个操作集合在一起,大致相当于:
// 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%肯定你想要实现的目标,但如果你只是想要对对象进行原子访问,那么你是否可以将对象本身的调用代码锁定?
// 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)
部分中,那么这种方法将是线程安全的.
如果您的方法可行,并发编程将非常简单.但事实并非如此,泰坦尼克号沉没的冰山,例如,你们班级的客户这样做:
objectRef.MyProperty += 1;
读 - 修改 - 写比赛非常明显,有更糟糕的.除了使它不可变之外,你绝对没有办法让你的属性成为线程安全的.你的客户需要处理头痛问题.被迫将这种责任委托给最不可能做到正确的程序员是并发编程的致命弱点.
正如其他人指出的那样,一旦从getter返回对象,就无法控制谁访问对象以及何时访问对象。要执行您想做的事情,您需要在对象本身内部放置一个锁。
也许我不了解完整情况,但是根据您的描述,听起来好像您不一定需要为每个字段都加锁。如果您只是通过getter和setter读写一组字段,则可能只需为这些字段加一个锁即可。很明显,您可能会不必要地以这种方式序列化线程的操作。但是同样,根据您的描述,这听起来也不像您正在积极地访问该对象。
我还建议使用事件而不是使用线程来轮询设备状态。使用轮询机制,每次线程查询设备时,您将被锁定。使用事件机制,一旦状态改变,对象将通知所有侦听器。届时,您的“轮询”线程(将不再进行轮询)将唤醒并获得新状态。这将更加有效。
举个例子...
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();