1

学校の課題のスレッドについて学習しようとしています。また、コレクションを空にするために2つのスレッドを取得しようとしています。私がこれまでに思いついたコードは、コレクションが変更されたという例外をスローします。

最初にロックされたコード部分にwhileループがありましたが、その後(もちろん;-))1つのスレッドだけがコレクションを空にします。

私の質問は、コレクションを空にする際に両方のスレッドが交互に繰り返されるループをどのように作成できるかということです。

class Program
{
    private static List<int> containers = new List<int>();

    static void Main(string[] args)
    {
        for (int i = 0; i < 100; i++)
        {
            containers.Add(i);
        }

        Thread t1 = new Thread(() => { foreach (int container in containers) { GeefContainer(); } });
        t1.Name = "Kraan 1";
        t1.Start();

        Thread t2 = new Thread(() => { foreach (int container in containers) { GeefContainer(); } });
        t2.Name = "Kraan 2";
        t2.Start();

        Console.Write("Press any key to continue...");
        Console.Read();
    }

    static void GeefContainer()
    {
        lock (containers)
        {
            int containerNummer = containers.Count - 1;

            //Container container = containers[containerNummer];

            //Console.Write("Container {0} opgehaald... Overladen", containerNummer);
            Console.WriteLine("Schip: Container {0} gegeven aan {1}", containerNummer, Thread.CurrentThread.Name);

            //Gevaarlijk, want methode aanroepen kan klappen
            containers.RemoveAt(containerNummer);
        }
    }
}
4

4 に答える 4

2

System.Collections.Concurrent名前空間にあるThreadSafeコレクションの使用は許可されていないと思います。

エントリがまだ残っているかどうかを確認するときは、コンテナコレクションへの排他的アクセスを取得する必要があります。ただし、ロックを解除する前に1つのスレッドがすべてのエントリを削除する排他的制御を取得することは望ましくありません。Monitor.Pulseを使用すると、コンテナのロックを待機している他のスレッドが「最初に移動」できるようになります。GeefContainersの次の実装を試してください。

static void GeefContainer()
{
    lock (containers)
    {
        while (containers.Any()) // using linq, similar to: while(container.Count > 0)
        {
            containers.RemoveAt(0); // remove the first element

            // allow other threads to take control
            Monitor.Pulse(containers); // http://msdn.microsoft.com/en-us/library/system.threading.monitor.pulse.aspx
                            // Wait for a pulse from the other thread
                            Monitor.Wait(container);
        }
    }
}

ああ、ループロジックを次の場所から削除します。

Thread t2 = new Thread(() => { foreach (int container in containers) { GeefContainer(); } });

GeefContainerを呼び出すだけで十分です。

これは、次の方法で視覚化できます。

  • スレッド1は「コレクション」へのロックを取得します
  • スレッド2は、「コレクション」への排他ロックを待機しているため、ブロックされています
  • スレッド1は「コレクション」からエントリを削除します
  • スレッド1は、「コレクション」のロックを解除し、新しい排他ロックを取得しようとします
  • スレッド2は「コレクション」へのロックを取得します
  • スレッド2は「コレクション」からエントリを削除します
  • スレッド2は、「コレクション」のロックを解除し、新しい排他ロックを取得しようとします
  • スレッド1は「コレクション」へのロックを取得します

于 2012-05-07T09:57:39.067 に答える
1

あなたが見ている例外は、列挙子によってスローされています。標準コレクションの列挙子には、列挙操作の途中でコレクションが変更されていないことを確認するためのチェックがあります(foreachあなたの場合は経由)。

スレッドをコレクションから交互に削除する必要があるため、スレッドが相互に信号を送ることができる何らかのメカニズムが必要になります。また、複数のコレクションから同時にコレクションにアクセスしないように注意する必要があります。プロパティでさえ、Count同期なしで安全に使用できるわけではありません。バリアクラスを使用すると、シグナリングが非常に簡単になります。lock同期には単純なもので十分です。これが私がこれを行う方法です。

public class Program
{
    public static void Main(string[] args)
    {
        var containers = new List<int>();

        for (int i = 0; i < 100; i++)
        {
            containers.Add(i);
        }

        var barrier = new Barrier(0);

        var t1 = new Thread(() => GeefContainers(containers, barrier));
        t1.Name = "Thread 1";
        t1.Start();

        var t2 = new Thread(() => GeefContainers(containers, barrier));
        t2.Name = "Thread 2";
        t2.Start();

        Console.Write("Press any key to continue...");
        Console.Read();
    }

    private static void GeefContainers(List<int> list, Barrier barrier)
    {
        barrier.AddParticipant();
        while (true)
        {
            lock (list)
            {
                if (list.Count > 0)
                {
                    list.RemoveAt(0);
                    Console.WriteLine(Thread.CurrentThread.Name + ": Count = " + list.Count.ToString());
                }
                else
                {
                    break;
                }
            }
            barrier.SignalAndWait();
        }
        barrier.RemoveParticipant();
    }

}

クラスはBarrier基本的にこれを何度も繰り返し発生させます。

|----|                 |----|                 |----|
| T1 |-->|         |-->| T1 |-->|         |-->| T1 |
|----|   |         |   |----|   |         |   |----|
         |-->(B)-->|            |-->(B)-->|         
|----|   |         |   |----|   |         |   |----|
| T2 |-->|         |-->| T2 |-->|         |-->| T2 |
|----|                 |----|                 |----|

上の図T1T2は、それぞれスレッド1と2の削除操作を表しています。(B)への呼び出しを表しますBarrier.SignalAndWait

于 2012-05-07T14:10:53.307 に答える
0

次のようにスレッドを変更するとどうなりますか?そうすれば、両方のスレッドがコレクションに対してアクションを実行するための時間を確保する必要があります。

Thread t1 = new Thread(() => { 
        while (containers.Count > 0)
        {
            GeefContainer(); 
            Thread.Sleep(150);
        }});
t1.Name = "Kraan 1";
t1.Start();

Thread t2 = new Thread(() => { 
        while (containers.Count > 0)
        {
            GeefContainer(); 
            Thread.Sleep(130);
        }});
t2.Name = "Kraan 2";
t2.Start();
于 2012-05-07T09:44:22.490 に答える
0

まず、thredの定義を次のように変更します。

new Thread(() => { while(containers.Count>0) { GeefContainer(); } });

次に、例外を回避するためにGeefContainer()を次のように書き直します。

static void GeefContainer()
{
    lock (containers)
    {
        int containerNummer = containers.Count - 1;

        if(containerNummer>=0) 
        {
            //Container container = containers[containerNummer];

            //Console.Write("Container {0} opgehaald... Overladen", containerNummer);
            Console.WriteLine("Schip: Container {0} gegeven aan {1}", containerNummer, Thread.CurrentThread.Name);

            //Gevaarlijk, want methode aanroepen kan klappen
            containers.RemoveAt(containerNummer);
        }
    }
}
于 2012-05-07T09:47:49.643 に答える