两个可交换线程光纤内的独家跨线程实体访问

时间:2014-07-05 17:55:30

标签: c# multithreading thread-safety deadlock

我使用的是使用光纤的网络库。光纤确保以同步和有序的方式执行所有已执行的操作:

interface IFiber 
{    
    Enqeue(Action action)
}

每个连接的对等体都有其请求光纤,在其中执行所有操作。

我也有一个应用级光纤。

每个对等体都有其(持久性)数据实体并对其进行操作。但我也需要从外部对等环境中访问它,即使它已断开连接(并且它的光纤自动处理)。

因此,当应用光纤(对等断开连接)或新光纤(对等连接)替换光纤时,我应该以某种方式维护字典并在光纤之间传输qeued操作。

我认为每个实体都存储一个Executor类。执行程序Enqeues操作,(De)RegisterFiber并在当前光纤内部执行操作。

public class Executor
{
    readonly object _lock = new object();
    readonly IFiber _applicaitonFiber;
    IFiber _currentFiber;
    Action _actions;

    public Executor(IFiber applicaitonFiber)
    {
        _currentFiber = _applicaitonFiber = applicaitonFiber;
    }

    public void SetFiber(IFiber fiber)
    {
        lock (_lock)
        {
            var fiberLocal = _currentFiber = fiber ?? _applicaitonFiber;
            if (_actions != null)
                _currentFiber.Enqueue(() => Execute(fiberLocal));
        }
    }

    public void Enqeue(Action action)
    {
        if (action == null) throw new ArgumentNullException("action");
        lock (_lock)
        {
            bool start = _actions == null;
            _actions += action;
            var fiberLocal = _currentFiber;
            if (start)
                _currentFiber.Enqueue(() => Execute(fiberLocal));
        }
    }

    void Execute(IFiber currentFiber)
    {
        lock (_lock)
        {
            if (currentFiber != _currentFiber) return;
            var a = _actions;
            if (a == null) return;
            _actions = null;
            // I can't release lock here. What if new fiber is registered before it is executed?
            a();
        }
    }
}

问题是如何在先前注册的光纤上执行操作时阻止新的光纤注册。

考虑这个死锁示例:

  1. 线程A:对实体1执行操作,使用Monitor阻止光纤交换。
  2. 线程B对实体2执行相同操作。
  3. 答:操作1需要访问/交换实体2的光纤。它等待B释放锁。
  4. B:操作2对实体1要求相同。它等待A。
  5. 我认为一种可能的解决方案是使SetFiber方法异步并通过_applicationFiber进行所有操作。

    public class Executor
    {
        readonly object _lock = new object();
        readonly IFiber _applicationFiber;
        IFiber _currentFiber;
        Action _actions;
    
        public Executor(IFiber applicaitonFiber)
        {
            _currentFiber = _applicationFiber = applicaitonFiber;
        }
    
        public IOperationResult<bool> SetFiber(IFiber fiber)
        {
            var r = new OperationResult<bool>();
            _applicationFiber.Enqueue(
                () =>
                {
                    lock (_lock)
                    {
                        var fiberLocal = _currentFiber = fiber ?? _applicationFiber;
                        if (_actions != null)
                            _currentFiber.Enqueue(() => Execute(fiberLocal));
                        r.Result = true; // async event
                    }
                });
            return r;
        }
    
        public void Enqeue(Action action)
        {
            if (action == null) throw new ArgumentNullException("action");
            _applicationFiber.Enqueue(
                () =>
                {
                    lock (_lock)
                    {
                        bool start = _actions == null;
                        _actions += action;
                        var fiberLocal = _currentFiber;
                        if (start)
                            _currentFiber.Enqueue(() => Execute(fiberLocal));
                    }
                });
        }
    
        void Execute(IFiber currentFiber)
        {
            lock (_lock)
            {
                if (currentFiber != _currentFiber) return; // replaced
                var a = _actions;
                if (a == null) return;
                _actions = null;
                a();
            }
        }
    }
    

    但我仍然不确定这个解决方案。如果我需要从动作内部执行大型数据库查询,该怎么办?它可以暂停整个应用光纤,直到锁定被释放。

    我可以在这里申请任何模式吗?

1 个答案:

答案 0 :(得分:0)

我认为它应该有效:

using System;
using System.Threading;
using ExitGames.Concurrency.Fibers;

    public class EntityFiberManager
    {
        readonly object _executionLock = new object();
        //readonly object _enqueueLock = new object();
        readonly IFiber _applicationFiber;
        IFiber _currentFiber;
        volatile Action _actions;

        public EntityFiberManager(IFiber applicaitonFiber)
        {
            _currentFiber = _applicationFiber = applicaitonFiber;
        }

        /// <summary>
        /// Removes the current set fiber if it's equal to <paramref name="fiber"/>.
        /// All queued actions will be rerouted to the application fiber.
        /// Can be called from anywhere.
        /// Disposed fiber should never be set with <see cref="AcquireForNewFiber"/> again.
        /// Doesn't block.
        /// </summary>
        public void ReleaseForDisposedFiber(IFiber fiber)
        {
            if (fiber == null) throw new ArgumentNullException("fiber");
            ReleaseForDisposedFiberInternal(fiber);
        }

        private void ReleaseForDisposedFiberInternal(IFiber fiber)
        {
            if ((_executingEntityFiberManager != null && _executingEntityFiberManager != this) || !Monitor.TryEnter(_executionLock, 1))
            {
                _applicationFiber.Enqueue(() => ReleaseForDisposedFiberInternal(fiber));
                return;
            }
            try
            {
                //lock (_enqueueLock)
                //{
                if (_currentFiber != fiber) return;
                _currentFiber = null;
                Thread.MemoryBarrier(); // do not reorder!
                if (_actions != null)
                    _applicationFiber.Enqueue(() => Execute(null));
                //}
            }
            finally
            {
                Monitor.Exit(_executionLock);
            }
        }

        /// <summary>
        /// Sets a new fiber. 
        /// All queued actions will be rerouted to that fiber.
        /// Can be called from anywhere except from another Executor queud action.
        /// Blocks until the current execution of queued actions is not finished.
        /// </summary>
        public void AcquireForNewFiber(IFiber fiber)
        {
            if (fiber == null) throw new ArgumentNullException("fiber");
            if (_executingEntityFiberManager != null && _executingEntityFiberManager != this) 
                throw new InvalidOperationException("Can't call this method on from queued actions on another instance");
            lock (_executionLock)
            //lock (_enqueueLock)
            {
                if (_currentFiber == fiber) return;
                var fiberLocal = _currentFiber = fiber;
                Thread.MemoryBarrier(); // do not reorder!
                if (_actions != null)
                    fiberLocal.Enqueue(() => Execute(fiberLocal));
            }
        }

        /// <summary>
        /// Enqueus an action to the current fiber.
        /// Doesn't block.
        /// </summary>
        public void Enqeue(Action action)
        {
            if (action == null) throw new ArgumentNullException("action");

            //lock (_enqueueLock)
            //{
            // we could add another lock
            // but we just need to ensure
            // that we properly detect when previous queue was empty

            // also delegate are immutable so we exchange references

            Action currentActions;
            Action newActions;
            do
            {
                Thread.Sleep(0);
                currentActions = _actions;
                newActions = currentActions + action;
            }
            while (Interlocked.CompareExchange(ref _actions, newActions, currentActions) != currentActions);

            bool start = currentActions == null;

            if (start)
            {
                // that's why we would want to use _enqueueLock
                // we don't want the current fiber to be replaced
                // imagine that when replacing queue was empty
                // than we read the fiber
                var fiber = _currentFiber;
                Thread.MemoryBarrier();
                if (fiber == null)
                    fiber = _applicationFiber;
                // and then replace writes its new fiber to memory
                // we have a wrong fiber here
                // and Execute will just quit
                // and all next Enqueue calls will do nothing
                // but now it's fixed with MemoryBarrier call. I think so.
                fiber.Enqueue(() => Execute(fiber));
            }
            //}
        }

        [ThreadStatic]
        static EntityFiberManager _executingEntityFiberManager;

        void Execute(IFiber currentFiber)
        {
            lock (_executionLock)
            {
                if (currentFiber != _currentFiber) return; // replaced
                var actions = Interlocked.Exchange(ref _actions, null);
                if (actions == null) return;
                if (_executingEntityFiberManager != null)
                    throw new InvalidOperationException("Already in execution process");
                _executingEntityFiberManager = this;
                try
                {
                    actions();
                }
                finally
                {
                    _executingEntityFiberManager = null;
                }
            }
        }
    }