多线程序列化和同步

本文关键字:同步 序列化 多线程 | 更新日期: 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();
    }
}