考虑一个节点网络(更新:“节点网络”是指同一应用程序域中的对象,而不是独立应用程序的网络)将对象彼此传递(并对它们进行一些处理)。 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;
}
}
但是:这看起来有点不直观(并且必须通过ervery调用传递auth实例很丑陋)。有没有更好的方法?还是这是“设计造成的”问题?
最佳答案
为了澄清您的问题:您寻求在C#中实现租用线程模型。简要说明处理对象并发访问的不同方法可能会有所帮助。
单线程:对对象的所有访问都必须在主线程上进行。
自由线程:对对象的任何访问都可以在任何线程上进行;对象的开发人员负责确保对象的内部一致性。使用该对象的代码的开发人员负责确保维持“外部一致性”。 (例如,当在多个线程上进行添加和删除操作时,自由线程字典必须始终保持其内部状态。外部调用者必须认识到“您是否包含此键?”问题的答案可能会由于对另一个线程。)
单元线程化:对对象的给定实例的所有访问都必须在创建对象的线程上进行,但是可以将不同的实例关联到不同的线程。对象的开发人员必须确保在对象之间共享的内部状态对于多线程访问是安全的,但是与给定实例关联的状态只能从单个线程读取或写入。通常,UI控件是单元线程的,并且必须位于UI线程的单元中。
租用线程:对对象的给定实例的访问必须在任何时候仅从单个线程发生,但是哪个线程可能会随着时间而改变
现在,让我们考虑一些您应该问的问题:
作为对象的作者,租赁模型是否是简化我的生活的合理方式?
可能吧。
租用模型的目的是在不承担实现和测试自由线程模型的成本的情况下获得多线程的一些好处。我不知道这些增加的收益和降低的成本是否合适。我个人对多线程情况下共享内存的价值表示怀疑。我认为整件事是个坏主意。但是,如果您陷入一个疯狂的想法,那就是在一个程序中修改共享内存的多个控制线程是件好事,那么租用模式也许适合您。
您正在编写的代码本质上是对对象调用者的帮助,使调用者更容易遵守租用模型的规则,并在遇到问题时更容易调试问题。通过为他们提供帮助,您可以降低成本,而成本却会适度增加。
实施这种援助的想法是一个很好的想法。早在1990年代Microsoft的VBScript和JScript的原始实现就使用了单元模型的变体,因此脚本引擎将从自由线程模式转换为单元线程模式。我们编写了很多代码来检测违反我们模型规则并立即产生错误的调用者,而不是让违例在将来的某个未指定点产生未定义的行为。
我的代码正确吗?
不,这不是线程安全的!强制执行租赁模型并检测到违反行为的代码本身不能假定调用方正确使用了租赁模型!您需要引入内存屏障,以确保读取和写入锁布尔的各种线程不会及时移动这些读取和写入。您的Own
方法充满了竞争条件。该代码需要非常非常仔细地设计并由专家进行审查。
我的建议-再次假设您希望完全追求共享内存多线程解决方案-是消除冗余布尔值;如果对象是无主的,则所有者应为null。我通常不提倡低锁解决方案,但是在这种情况下,您可以考虑使用Interlocked.CompareExchange
与新所有者进行原子上的原子比较和交换。如果对null的比较失败,则您的API用户的竞争条件违反了租借模型。这引入了存储障碍。
关于c# - 防止并发访问被传递的对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20856856/