修改集合的多个线程

时间:2012-05-07 09:36:09

标签: multithreading c#-4.0

我试图了解学校作业的线索,我试图让两个线程清空一个集合。到目前为止我提出的代码抛出一个异常,说该集合已被修改。

首先我在锁定的代码部分中有一个while循环,但是然后(当然;-))只有一个线程清空了该集合。

我的问题是,我怎么能有一个循环,让线程轮流清空集合?

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 个答案:

答案 0 :(得分:2)

我假设您不允许使用System.Collections.Concurrent命名空间中的任何ThreadSafe集合。

在检查是否还有条目时,您需要获得对容器集合的独占访问权限。但是,在释放锁定之前,您不希望1个线程采取独占控制来删除所有条目。 Monitor.Pulse可用于允许等待锁定容器的其他线程首先进入&#39;。尝试以下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获得对“&#39;
  • ”的锁定
  • 线程2被屏蔽,因为它正在等待对集合的独占锁定&#39;
  • 主题1会从&#39;
  • 中删除条目
  • 线程1释放它锁定集合&#39;并试图获得一个新的独家锁
  • 线程2锁定了&#39;集合&#39;
  • 主题2会从&#39;
  • 中删除条目
  • 线程2将其锁定在&#39;集合&#39;并试图获得一个新的独家锁
  • 主题1获得对“&#39;
  • ”的锁定

答案 1 :(得分:1)

您看到的异常是由枚举器抛出的。标准集合上的枚举器进行检查以确保在枚举操作过程中未修改集合(在您的情况下通过foreach)。

由于您希望让线程交替从集合中移除,因此您需要某种允许线程相互发信号的机制。我们还必须注意不要同时从多个集合中访问集合。即使Count属性在没有同步的情况下也可以安全使用。 Barrier类使信令非常简单。一个简单的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的调用。

答案 2 :(得分: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();

答案 3 :(得分:0)

首先,按如下方式更改您的定义:

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);
        }
    }
}