2012-05-07 78 views
1

我想了解有关学校任务的线程,我试图让两个线程清空集合。到目前为止,我提出的代码引发了一个异常,表示该集合已被修改。多线程修改集合

首先,我在锁定的代码部分有一个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); 
     } 
    } 
} 

回答

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涨势锁定为“收藏”

+0

我很抱歉,我是那个犯了错误的人。虽然,现在我修好了它,但我注意到只有线程1清空了这个集合。也许我应该在某个地方睡一觉? –

+0

@ ImNotANumber.OhWait ... 0792588 - 对不起,我的坏。您确实需要让线程等待来自其他线程的脉冲。简单地把Wait语句放在循环的底部,看到我正在阅读关于线程的修改代码 – Polity

+0

,以及http://www.albahari.com/threading/part4.aspx#_Signaling_with_Wait_and_Pulse上的等待和脉冲方法,并且它表示同样的事情,虽然我犯了在Pulse()之前放置Wait()的错误。你拥有它的方式效果很好。 –

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(); 
+0

这接近我想要的。它与上面的解决方案非常相似,但是由于thread.sleep,它运行的非常好,可以编织进出线程。 –

+0

谢谢你的帮助!我用等待和脉冲解决方案指出了这个问题,因为它消除了两个循环。我想给你投票,但我没有足够的积分来做到这一点。 –

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

谢谢,这个作品!唯一的事情,但;为什么它使用while循环而不是foreach?这与获得统计员有关吗? –

+0

其实我猜如果你会尝试使用上面的'GeefContainer()'foreach foreach再次看不到异常。这是因为:在thread1启动了数组中的100个元素的foreach循环之后,thread2删除了一些元素。然后在某个点循环继续,尽管数组被清空。然后'containerNummer'变为负数,'containers.RemoveAt(containerNummer)'抛出异常。 这就是我的眼睛... – mostar

+0

谢谢你的洞察力和你的帮助!我用等待和脉冲解决方案指出了这个问题,因为它消除了两个循环。我想给你投票,但我没有足够的积分来做到这一点。 –

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

+0

谢谢你的帮助,这非常有见地!我想给你一个表决权,但我还没有做到这一点。对不起。 –

+0

@ ImNotANumber.OhWait ... 0792588:谢谢,但我可以不在意代表,所以不要流汗。我只是喜欢回答问题和帮助人。 –