防止对象的并发访问

时间:2013-12-31 11:45:02

标签: c# object locking restrict ownership

考虑一个节点网络(更新:'节点网络'意味着同一应用程序域中的对象,而不是独立应用程序的网络)将对象相互传递(并对其进行一些处理)他们)。 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实例是丑陋的)。有更好的方法吗?或者这是“由设计制造”的问题?

2 个答案:

答案 0 :(得分:13)

澄清您的问题:您希望在C#中实施租赁线程模型。处理并发访问对象的不同方法的简要说明可能会有所帮助。

  • 单线程:对象的所有访问必须在主线程上进行。

  • 自由线程:任何线程都可能发生对该对象的任何访问;对象的开发者负责确保对象的内部一致性。消耗该对象的代码的开发者负责确保维持“外部一致性”。 (例如,当在多个线程上发生添加和删除时,自由线程字典必须始终保持其内部状态。外部调用者必须认识到问题的答案“你是否包含此密钥?”可能会因为编辑而改变另一个线程。)

  • 单元线程化:对象的给定实例的所有访问必须在创建对象的线程上进行,但不同的实例可以关联到不同的线程。对象的开发人员必须确保对象之间共享的内部状态对于多线程访问是安全的,但是与给定实例关联的状态只能从单个线程读取或写入。通常,UI控件是单元线程的,必须位于UI线程的公寓中。

  • 租用线程:对对象的给定实例的访问必须在任何时候只从一个线程发生,但是哪个线程可能随时间发生变化

现在让我们考虑一些你应该问的问题:

  • 作为对象的作者,租赁模式是否是简化生活的合理方式?

可能。

租赁模型的目的是在不承担实现和测试自由线程模型的成本的情况下实现多线程的一些好处。我不知道,这些增加的收益和降低的成本是否合适。我个人对多线程情况下共享内存的价值持怀疑态度;我认为整件事情都是个坏主意。但是,如果你被一个程序中的多个控制线程修改为共享内存的疯狂想法,那么也许租赁模型适合你。

您正在编写的代码本质上是对对象调用者的一种帮助,使调用者更容易遵守租赁模型的规则,并在他们流浪时更容易调试问题。通过向他们提供帮助,您可以降低成本,适度增加自己的成本。

实施这种援助的想法很好。早在20世纪90年代,微软的VBScript和JScript的原始实现使用了公寓模型的变体,从而脚本引擎将从自由线程模式转变为公寓线程模式。我们编写了大量代码来检测违反模型规则并立即产生错误的调用者,而不是允许违规在未来某些未指定的点上产生未定义的行为。

  • 我的代码是否正确?

没有。这不是线程安全的! 强制执行租借模型并检测违规行为的代码本身并不能假设调用者正确使用租赁模型!您需要引入内存屏障以确保读取和写入锁定的各种线程bool并没有及时移动那些读写。你的Own方法充满了竞争条件。此代码需要非常,非常精心设计并由专家审核。

我的建议 - 再次假设您希望寻求共享内存多线程解决方案 - 是为了消除多余的bool;如果对象是无主的,则所有者应为null。我通常不提倡使用低锁解决方案,但在这种情况下,您可以考虑查看Interlocked.CompareExchange与新所有者在字段上进行原子比较和交换。如果比较为null失败,则API的用户具有违反租赁模型的竞争条件。这引入了内存屏障。

答案 1 :(得分:0)

也许您的示例过于简化,您确实需要这种复杂的所有权,但以下代码应该可以完成这项工作:

class TransferredObject
{
    private object _lockObject = new object();

    public void DoSomething()
    {
        lock(_lockObject)
        {
            // TODO: your code here...
        }
    }
}

您的TransferredObject有一个原子方法DoSomething,可以更改某些状态,并且不应该同时运行多次。因此,只需将lock放入其中即可同步关键部分。

请参阅http://msdn.microsoft.com/en-us/library/c5kehkcz%28v=vs.90%29.aspx