2012-05-07 76 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:謝謝,但我可以不在意代表,所以不要流汗。我只是喜歡回答問題和幫助人。 –