多线程序列化和同步
本文关键字:同步 序列化 多线程 | 更新日期: 2023-09-27 18:03:44
我有以下情况:
一个单例类,我们称它为Coordinator,它有多个线程正在加入。该类的作用是以线程安全的方式"初始化"某个实体(EntityObject)。假设我们有5个线程试图初始化一个EntityObject。应该只允许一个线程初始化EntityObject,而其他4个线程应该等待,直到初始化完成。实体对象的名称是唯一的。
下面是一些代码来说明这一点:public class EntityObject
{
public EntityObject()
{
IsInitialized = false;
Name = string.Empty;
}
public bool IsInitialized { get; set; }
public string Name { get; set; }
}
public class InitializeArguments
{
public EntityObject Entity { get; set; }
}
public class Coordinator
{
public void initialize(InitializeArguments args)
{
if (!args.Entity.IsInitialized)
{
//initializeCode goes here
//only one thread is allowed to initialize an EntityObject with a certain Name
//the other threads have to wait until initialization is done
args.Entity.IsInitialized = true;
}
}
}
class Program
{
static void Main(string[] args)
{
List<Task> allTask = new List<Task>();
Coordinator coordinator = new Coordinator();
EntityObject entity1 = new EntityObject() { IsInitialized = false, Name = "entity1" };
EntityObject entity2 = new EntityObject() { IsInitialized = false, Name = "entity2" };
EntityObject entity3 = new EntityObject() { IsInitialized = false, Name = "entity3" };
for (int i = 0; i < 4; i++)
{
var task = Task.Factory.StartNew(() =>
{
InitializeArguments initArg = new InitializeArguments() { Entity = entity1 };
coordinator.initialize(initArg);
});
allTask.Add(task);
}
for (int i = 0; i < 4; i++)
{
var task = Task.Factory.StartNew(() =>
{
InitializeArguments initArg = new InitializeArguments() { Entity = entity2 };
coordinator.initialize(initArg);
});
allTask.Add(task);
}
for (int i = 0; i < 4; i++)
{
var task = Task.Factory.StartNew(() =>
{
InitializeArguments initArg = new InitializeArguments() { Entity = entity3 };
coordinator.initialize(initArg);
});
allTask.Add(task);
}
Task.WaitAll(allTask.ToArray());
Console.ReadLine();
}
}
在这种情况下,entity1,entity2,entity3应该只初始化一次。
我想使用Dictionary<string, ManualResetEventSlim>
来实现这一点,但我不能使它工作。
获取一个锁,只允许一个线程进入临界区:
public class Coordinator
{
private static object lockObj = new Object();
public void initialize(InitializeArguments args)
{
lock(lockObj)
{
if (!args.Entity.IsInitialized)
{
...
}
}
}
}
考虑到你的评论:你可以锁定实体本身,但这被认为是不好的做法(参见上面的链接并记住这篇文章):
一般来说,避免锁定公共类型或超出您的代码的控制。常见的构造是lock (this), lock (typeof)(MyType))和lock ("myLock")违反了这个准则
所以也许你可以用
lock(args.Entity)
代替lock(lockObj)
…但是不要锁定实体的名称——也许它在您的控制范围内是唯一的,但是谁保证它在整个过程中是唯一的呢?
使用工厂并实现双锁模式如何:
public class EntityFactory
{
private static Dictionary<string, EntityObject> _entityObjects = new Dictionary<string, EntityObject>();
private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
public EntityObject CreateEntity(string name)
{
EntityObject result = null;
try
{
if (!_entityObjects.TryGetValue(name, out result))
{
Lock.EnterWriteLock();
try
{
if (!_entityObjects.TryGetValue(name, out result))
{
// initialisation code here
result = new EntityObject() {Name = name};
_entityObjects[name] = result;
}
}
finally
{
Lock.ExitWriteLock();
}
}
}
finally
{
Lock.ExitUpgradeableReadLock();
}
return result;
}
}
工厂应该是单例的。它有一个实体对象的静态字典(按名称键控)。它检查一个实体对象是否存在,如果存在则返回它,否则它获得一个写锁,然后再次检查字典。因为另一个线程可能在维护写锁时创建了一个。如果字典中仍然没有一个,它会创建一个,替换我用EntityObject初始化代码放置注释的代码。然后将实体对象存储在字典中并返回。
修改@KevinHolditch的代码来缓存对象创建过程与实际对象的对比。摆脱了Lock.ExitUpgradeableReadLock();
,因为它产生了一个错误,因为没有读锁。假设EntityObject
实现了IEquatable
,那么应该不会有问题,除非明确要求引用相等。
public class EntityFactory
{
private static Dictionary<string, Func<EntityObject>> _entityObjects = new Dictionary<string, Func<EntityObject>>();
private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
public EntityObject CreateEntity(string name)
{
Func<EntityObject> result = () => null;
if (!_entityObjects.TryGetValue(name, out result))
{
Lock.EnterWriteLock();
try
{
if (!_entityObjects.TryGetValue(name, out result))
{
result = () =>
{
// initialisation code here
var entity = new EntityObject { Name = name };
return entity;
};
_entityObjects[name] = result;
}
}
finally
{
Lock.ExitWriteLock();
}
}
return result();
}
}