我正在开发一个具有许多可以同时交互的不同实体的应用程序。我想知道这些实体以线程安全的方式相互交互的最佳方式是什么。
为了演示一些简化的代码,请考虑每个实体都有自己的光纤和一些状态:
class Fiber
{
private ActionBlock<Action> _workQueue;
public Fiber()
{
_workQueue = new ActionBlock<Action>((a) => a());
}
public void Enqueue(Action a)
{
_workQueue.Post(a);
}
public void Stop()
{
_workQueue.Complete();
}
}
class EntityState
{
public int x { get; set; }
}
class Entity
{
private Fiber _fiber = new Fiber();
public EntityState State { get; set; }
// ...
}
假设动作被任意排队到实体光纤上。一个这样的动作可能是实体必须修改另一个实体的状态。我已经考虑过以线程安全方式执行此操作的两个选项。
选项1:仅允许通过线程安全包装器进行状态突变,I.E。
class Entity
{
private Fiber _fiber = new Fiber();
private ReaderWriterLockSlim _stateLock = new ReaderWriterLockSlim();
private EntityState _state = new EntityState();
public T ReadState<T>(Func<EntityState, T> reader)
{
T result = default(T);
_stateLock.EnterReadLock();
result = reader(_state);
_stateLock.ExitReadLock();
return result;
}
public void WriteState(Action<EntityState> writer)
{
_stateLock.EnterWriteLock();
writer(_state);
_stateLock.ExitWriteLock();
}
// ...
}
选项2:只允许状态变异,将其调度到拥有实体的光纤上并返回一个Future,以便mutator可以看到变异发生的时间,I.E。
class Future<T>
{
public T Value { get; set; }
}
class Entity
{
private Fiber _fiber = new Fiber();
private EntityState _state = new EntityState();
public Future<T> AccessState<T>(Func<EntityState, T> accessor)
{
Future<T> future = new Future<T>();
_fiber.Enqueue(() => future.Value = accessor(_state));
return future;
}
// ...
}
我还没考虑过其他什么选择?有没有办法做到这一点?我应该这样做吗?
答案 0 :(得分:1)
您的所有选择都会给您带来痛苦。
正确的方法是将线程代码与应用程序代码完全分开。将任务放在单线程光纤中。任务在所有涉及的实体中同步执行所有作业。任务完成后,您可以异步执行IO。 我已经为这种方法写了a library。
答案 1 :(得分:1)
您可以将突变排入拥有光纤,然后将其延续到您自己的光纤中。这样你就没有任何明确的锁。
但是:这种光纤方法并不比在访问实体之前获取lock
更好。 (锁在内部包含一个队列。)
此外,您不能以这种方式进行跨实体交易。使用锁方法,您可以收集参与事务的所有实体的锁,将它们排序为总订单并将它们全部锁定。这为您提供了没有死锁的跨实体事务。
答案 2 :(得分:0)
目前,我最终选择了2或更少,但是如果访问状态不会阻止我同步设置结果。即。
using System;
using System.Threading;
namespace Server.Utility
{
public class ThreadSafeWrapper<ObjectType>
{
private ObjectType m_object;
private Fiber m_fiber;
public ThreadSafeWrapper(ObjectType obj, Fiber fiber)
{
m_object = obj;
m_fiber = fiber;
}
public Future<ReturnType> Transaction<ReturnType>(Func<ObjectType, ReturnType> accessor)
{
Future<ReturnType> future = new Future<ReturnType>();
ReturnType synchronousResult = default(ReturnType);
if (Monitor.TryEnter(m_object))
{
synchronousResult = accessor(m_object);
Monitor.Exit(m_object);
future.SetResult(synchronousResult);
}
else
{
m_fiber.Enqueue(() =>
{
ReturnType result = default(ReturnType);
lock (m_object)
{
result = accessor(m_object);
}
future.SetResult(result);
});
}
return future;
}
public void Transaction(Action<ObjectType> accessor)
{
if (Monitor.TryEnter(m_object))
{
accessor(m_object);
Monitor.Exit(m_object);
}
else
{
m_fiber.Enqueue(() =>
{
lock (m_object)
{
accessor(m_object);
}
});
}
}
}
}