想象一下节点网络(更新:’节点网络’意味着同一应用程序域中的对象,而不是独立应用程序的网络)将对象相互传递(并对它们进行一些处理). C#中是否存在用于将对象的访问限制为仅实际处理它的节点的模式?
主要动机:确保线程安全(无并发访问)和对象一致性(关于存储在其中的数据).
V1:我想到这样的事情:
class TransferredObject
{
public class AuthLock
{
public bool AllowOwnerChange { get; private set; }
public void Unlock() { AllowOwnerChange = true; }
}
private AuthLock currentOwner;
public AuthLock Own()
{
if (currentOwner != null && !currentOwner.AllowOwnerChange)
throw new Exception("Cannot change owner, current lock is not released.");
return currentOwner = new AuthLock();
}
public void DoSomething(AuthLock authentification)
{
if (currentOwner != authentification)
throw new Exception("Don't you dare!");
// be sure, that this is only executed by the one holding the lock
// Do something...
}
}
class ProcessingNode
{
public void UseTheObject(TransferredObject x)
{
// take ownership
var auth = x.Own();
// do processing
x.DoSomething(auth);
// release ownership
auth.Unlock();
}
}
V2:相当大的开销 – 一个不太“严格”的实现可能是忽略检查并依赖于“锁定/解锁”逻辑:
class TransferredObject
{
private bool isLocked;
public Lock()
{
if(isLocked)
throw new Exception("Cannot lock, object is already locked.");
isLocked = true;
}
public Unlock() { isLocked = false; }
public void DoSomething()
{
if (isLocked)
throw new Exception("Don't you dare!");
// Do something...
}
}
class ProcessingNode
{
public void UseTheObject(TransferredObject x)
{
// take ownership
x.Lock = true;
// do processing
x.DoSomething();
// release ownership
x.Unlock = true;
}
}
但是:这看起来有点不直观(并且必须传递带有错误调用的auth实例很难看).有更好的方法吗?或者这是“由设计制造”的问题?
解决方法:
澄清您的问题:您试图在C#中实施租赁线程模型.处理并发访问对象的不同方法的简要说明可能会有所帮助.
>单线程:对象的所有访问必须在主线程上进行.
>*线程:对任何线程的任何访问都可能发生;对象的开发者负责确保对象的内部一致性.消耗该对象的代码的开发者负责确保维持“外部一致性”. (例如,当在多个线程上发生添加和删除时,*线程字典必须始终保持其内部状态.外部调用者必须认识到问题的答案“你是否包含此密钥?”可能会因为编辑而改变另一个线程.)
>单元线程:对象的给定实例的所有访问必须发生在创建对象的线程上,但不同的实例可以关联到不同的线程.对象的开发人员必须确保对象之间共享的内部状态对于多线程访问是安全的,但是与给定实例关联的状态只能从单个线程读取或写入.通常,UI控件是单元线程的,必须位于UI线程的公寓中.
> Rental threads:对象的给定实例的访问必须在任何时候只从一个线程发生,但是哪个线程可能随时间而改变
那么现在让我们考虑一些你应该问的问题:
>作为对象的作者,租赁模式是否是简化生活的合理方式?
有可能.
租赁模型的目的是在不承担实现和测试*线程模型的成本的情况下实现多线程的一些好处.我不知道,这些增加的收益和降低的成本是否合适.我个人对多线程情况下共享内存的价值持怀疑态度;我认为整件事情都是个坏主意.但是如果你被一个疯狂的想法所吸引,即一个程序中的多个控制线程修改共享内存是好的,那么租赁模型可能适合你.
您编写的代码本质上是对对象调用者的一种帮助,使调用者更容易遵守租赁模型的规则,并在他们流浪时更容易调试问题.通过向他们提供帮助,您可以降低成本,适度增加自己的成本.
实施这种援助的想法很好.早在20世纪90年代,微软的VBScript和JScript的原始实现使用了公寓模型的变体,从而脚本引擎将从*线程模式转变为公寓线程模式.我们编写了大量代码来检测违反模型规则并立即产生错误的调用者,而不是允许违规在未来某些未指定的点上产生未定义的行为.
>我的代码是否正确?
不,这不是线程安全的!强制执行租赁模型并检测违规行为的代码本身不能假定调用者正确使用租赁模型!您需要引入内存屏障,以确保读取和写入锁定bool的各种线程不会及时移动这些读取和写入.你自己的方法充满了竞争条件.此代码需要由专家非常,非常仔细地设计和审查.
我的建议 – 再次假设您希望寻求共享内存多线程解决方案 – 是为了消除多余的bool;如果对象是无主的,则所有者应为null.我通常不提倡使用低锁解决方案,但在这种情况下,您可能会考虑使用Interlocked.CompareExchange在字段上与新所有者进行原子比较和交换.如果比较为null失败,则API的用户具有违反租赁模型的竞争条件.这引入了内存屏障.