0

次のような状況があります。

シングルトン クラス、複数のスレッドがアクセスしているコーディネーターと呼びましょう。このクラスの役割は、特定のエンティティ (EntityObject) をスレッドセーフな方法で「初期化」することです。EntityObject を初期化しようとしている 5 つのスレッドがあるとします。1 つのスレッドのみが EntityObject の初期化を許可され、他の 4 つのスレッドは初期化が完了するまで待機する必要があります。EntityObjects は、その名前によって一意です。

これを説明するコードを次に示します。

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 は 1 回だけ初期化する必要があります。

これを実現するために a を使用することを考えていましたが、 Dictionary<string, ManualResetEventSlim>機能させることができません。

4

3 に答える 3

3

ロックを取得して、1 つのスレッドのみがクリティカル セクションに入るのを許可します。

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)...しかし、エンティティの名前にロックを取得しないでください-おそらくそれはあなたの制御内で一意ですが、プロセス全体で一意であることを誰が保証しますか?

于 2013-03-12T15:53:22.727 に答える
1

ファクトリを使用して、次のようなダブルロックパターンを実装するのはどうですか。

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;

    }
}

工場はシングルトンでなければなりません。エンティティオブジェクトの静的ディクショナリ(名前でキー設定)があります。エンティティオブジェクトが存在するかどうかを確認し、存在する場合はそれを返します。存在しない場合は、書き込みロックを取得してから、ディクショナリを再度確認します。書き込みロックが維持されている間に別のスレッドが作成した可能性があるため。ディクショナリにまだ1つもない場合は、それを作成します。コメントを付けたコードをEntityObject初期化コードに置き換えます。次に、エンティティオブジェクトをディクショナリに格納して返します。

于 2013-03-12T15:57:53.037 に答える
0

オブジェクト作成プロセスと実際のオブジェクトをキャッシュするように @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();
    }
}
于 2013-03-12T16:20:52.173 に答える