1. 程式人生 > >c#集合類的執行緒安全(整理)

c#集合類的執行緒安全(整理)

即位於System.Collections名稱空間下的集合,如Hashtable,ArrayList,Stack,Queue等.其均提供了執行緒同步的一個實現

集合執行緒同步的問題

public class Demo8
{
    ArrayList list = new ArrayList(1000000);
    public Demo8()
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(Task1));
        ThreadPool.QueueUserWorkItem(new WaitCallback(Task2));
    }

    public void 
Task1(object obj) { for (int i = 0; i < 500000; i++) { list.Add(i); } Console.WriteLine(DateTime.Now); Console.WriteLine("Task1 count {0}", list.Count); } public void Task2(object obj) { for (int i = 0; i < 500000; i++) { list.Add(i); } Console
.WriteLine("Task2 count {0}", list.Count); } }

image

與預期結果不同

調整為執行緒同步的集合

每種資料型別都包含一個靜態的Synchronized方法,如

ArrayList list = ArrayList.Synchronized(new ArrayList(1000000));

調整後的結果

image 
以下為注意點:

  1. IsSynchronized判斷集合是否為執行緒同步
  2. 其內部通過給SyncRoot屬性加鎖進行同步(即Monitor.Enter)

自己控制鎖

public class Demo8
{
    ArrayList list = new 
ArrayList(1000000); public Demo8() { ThreadPool.QueueUserWorkItem(new WaitCallback(Task1)); ThreadPool.QueueUserWorkItem(new WaitCallback(Task2)); } public void Task1(object obj) { lock (list.SyncRoot) { for (int i = 0; i < 500000; i++) { list.Add(i); } } Console.WriteLine(DateTime.Now); Console.WriteLine("Task1 count {0}", list.Count); } public void Task2(object obj) { lock (list.SyncRoot) { for (int i = 0; i < 500000; i++) { list.Add(i); } } Console.WriteLine("Task2 count {0}", list.Count); } }

image

這樣的結果顯然好看點.內部實現是在Add方法中做鎖定.效果自然不是很好.

其他集合類也是類似的操作

泛型集合

可以看到原非泛型集合內部的執行緒同步集合,在每次操作均採用鎖操作,但我們並非每個操作都需要鎖,比如上面的2個執行緒操作.只需要2個鎖就可以了,但使用內部集合的話則需要鎖很多次,帶來了效能問題.在.net 2.0泛型集合中,內部不再支援執行緒同步的集合,即使內部實現了執行緒同步的集合如List<T>的實現也為開發出來,即把lock的這個操作轉嫁給開發者上面了.其實這樣反而可以讓我們更加了解執行緒同步的問題,如果真有需要的話,也可以自己實現一個了...

NET Framework 4 中的並行程式設計9---執行緒安全集合類

作者: 
李嘉良 
發表於: 
2012-02-29, 18:14 
評論: 
瀏覽: 
414 
RSS: 
0

在.Net 4中,新增System.Collections.Concurrent 名稱空間中提供多個執行緒安全集合類,這些類提供了很多有用的方法用於訪問集合中的元素,從而可以避免使用傳統的鎖(lock)機制等方式來處理併發訪問集合.因此當有多個執行緒併發訪問集合時,應首先考慮使用這些類代替 System.Collections 和 System.Collections.Generic 名稱空間中的對應型別.具體如下:

1. ConcurrentQueue

表示執行緒安全的先進先出(FIFO)佇列.程式碼如下:

           ConcurrentQueue<int> sharedQueue = new ConcurrentQueue<int>();

            for (int i = 0; i < 1000; i++)

            {

                sharedQueue.Enqueue(i);

            }

            int itemCount = 0;

            Task[] tasks = new Task[10];

            for (int i = 0; i < tasks.Length; i++)

            {

                tasks[i] = new Task(() =>

                {

                    while (sharedQueue.Count > 0)

                    {

                        int queueElement;

                        bool gotElement = sharedQueue.TryDequeue(out queueElement);

                        if (gotElement)

                        {

                            Interlocked.Increment(ref itemCount);

                        }

                    }

                });

                tasks[i].Start();

            }

            Task.WaitAll(tasks);

            Console.WriteLine("Items processed:{0}", itemCount);

            Console.WriteLine("Press Enter to finish");

            Console.ReadLine();

該類有兩個重要的方法用來訪問佇列中的元素.分別是:

Ø TryDequeue 嘗試移除並返回位於佇列頭開始處的物件.

Ø TryPeek嘗試返回位於佇列頭開始處的物件但不將其移除.

現在,在多工訪問集合元素時,我們只需要使用TryDequeue或TryPeek方法,就可以安全的訪問集合中的元素了.

2. ConcurrentStack

表示執行緒安全的後進先出(LIFO)棧.它也有幾個有用的方法,分別是:

Ø TryPeek:嘗試返回棧頂處的元素,但不移除.

Ø TryPop: 嘗試返回棧頂處的元素並移除.

Ø TryPopRange: 嘗試返回棧頂處開始指定範圍的元素並移除.

在訪問集合中的元素時,我們就可以上述方法.具體程式碼例項於上面的ConcurrentQueue類似,就不重複了.

3. ConcurrentBag

實現的是一個無序的集合類.程式碼如下:

            ConcurrentBag<int> sharedBag = new ConcurrentBag<int>();

            for (int i = 0; i < 1000; i++)

            {

                sharedBag.Add(i);

            }

            int itemCount = 0;

            Task[] tasks = new Task[10];

            for (int i = 0; i < tasks.Length; i++)

            {

                tasks[i] = new Task(() =>

                {

                   while(sharedBag.Count>0)

                    {

                        int queueElement;

                        bool gotElement = sharedBag.TryTake(out queueElement);

                       if (gotElement)

                            Interlocked.Increment(ref itemCount);

                    }

                });

                tasks[i].Start();

            }

            Task.WaitAll(tasks);

            Console.WriteLine("Items processed:{0}", itemCount);

            Console.WriteLine("Press Enter to finish");

            Console.ReadLine();

該類有兩個重要的方法用來訪問佇列中的元素.分別是:

Ø TryTake 嘗試移除並返回位於佇列頭開始處的物件.

Ø TryPeek嘗試返回位於佇列頭開始處的物件但不將其移除.

4. ConcurrentDictionary

實現的是一個鍵-值集合類.它提供的方法有:

Ø TryAdd:嘗試向集合新增一個鍵-值

Ø TryGetValue:嘗試返回指定鍵的值.

Ø TryRemove:嘗試移除指定鍵處的元素.

Ø TryUpdate:嘗試更新指定鍵的值.

程式碼如下:

        class BankAccount

        {

            public int Balance

            {

                get;

                set;

            }

        }

 static void DictTest()

        {

            BankAccount account = new BankAccount();

            ConcurrentDictionary<objectint> sharedDict = new ConcurrentDictionary<objectint>();

            Task<int>[] tasks = new Task<int>[10];

            for (int i = 0; i < tasks.Length; i++)

            {

                sharedDict.TryAdd(i, account.Balance);

                tasks[i] = new Task<int>((keyObj) =>

                {

                    int currentValue;

                    bool gotValue;

                    for (int j = 0; j < 1000; j++)

                    {

                        gotValue = sharedDict.TryGetValue(keyObj, out currentValue);

                        sharedDict.TryUpdate(keyObj, currentValue + 1, currentValue);

                    }

                    int result;

                    gotValue = sharedDict.TryGetValue(keyObj, out result);

                    if (gotValue)

                    {

                        return result;

                    }

                    else

                    {

                        throw new Exception(String.Format("No data item available for key {0}", keyObj));

                    }

                }, i);

                tasks[i].Start();

            }

            for (int i = 0; i < tasks.Length; i++)

            {

                account.Balance += tasks[i].Result;

            }

            Console.WriteLine("Expected value {0}, Balance: {1}", 10000, account.Balance);

            Console.WriteLine("Press enter to finish");

            Console.ReadLine();

}

通過上述提供的安全類,我們可以方便的併發訪問集合中的元素,而不需要以前的Synchronized方法或者lock(SyncRoot)等處理方式 http://blog.csdn.net/web718/article/details/5105578.  

HashTable 中的 key/value均為 object型別,由包含集合元素的儲存桶組成。儲存桶是 HashTable中各元素的虛擬子組,與大多數集合中進行的搜尋和檢索相比,儲存桶可令搜尋和檢索更為便捷。每一儲存桶都與一個雜湊程式碼關聯,該雜湊程式碼是使用雜湊函式生成的並基於該元素的鍵。 HashTable的優點就在於其索引的方式,速度非常快。如果以任意型別鍵值訪問其中元素會快於其他集合,特別是當資料量特別大的時候,效率差別尤其大。

HashTable的應用場合有:做物件快取,樹遞迴演算法的替代,和各種需提升效率的場合。

 // Hashtable sample 
 
    System.Collections.Hashtable ht =  new System.Collections.Hashtable();
 
     
// --Be careful: Keys can't be duplicated, and can't be null---- 
 
    ht.Add( 1 " apple " );
     ht.Add(
 2 " banana " );
     ht.Add(
 3 " orange " );
     
     
// Modify item value: 
 
if (ht.ContainsKey( 1 ))
         ht[
 1 =  " appleBad " ;
 
     
// The following code will return null oValue, no exception 
 
object oValue = ht[ 5 ];  
     
     
// traversal 1: 
 
foreach (DictionaryEntry de in ht)
     
{
         Console.WriteLine(de.Key);
         Console.WriteLine(de.Value);
     }
 

 
     
// traversal 2: 
 
    System.Collections.IDictionaryEnumerator d = ht.GetEnumerator();
     
while (d.MoveNext())
     
{
         Console.WriteLine(
 " key:{0} value:{1} " , d.Entry.Key, d.Entry.Value);
     }
 

 
     
// Clear items 
 
    ht.Clear();


Dictionary 和 HashTable內部實現差不多,但前者無需裝箱拆箱操作,效率略高一點。

 // Dictionary sample 
 
    System.Collections.Generic.Dictionary < int string > fruits =  
         
new System.Collections.Generic.Dictionary < int string > ();
 
     fruits.Add(
 1 " apple " );
     fruits.Add(
 2 " banana " );
     fruits.Add(
 3 " orange " );
 
     
foreach ( int in fruits.Keys)
     
{
         Console.WriteLine(
 " key:{0} value:{1} " , i, fruits);
     }
 

 
     
if (fruits.ContainsKey( 1 ))
     
{
         Console.WriteLine(
 " contain this key. " );
     }

ArrayList 是一維變長陣列,內部值為 object型別,效率一般:

 // ArrayList 
 
    System.Collections.ArrayList list =  new System.Collections.ArrayList();
     list.Add(
 1 ); // object type 
 
    list.Add( 2 );
     
for ( int =  0 ; i < list.Count; i ++ )
     
{
         Console.WriteLine(list[i]);
     }



HashTable是經過優化的,訪問下標的物件先雜湊過,所以內部是無序雜湊的,保證了高效率,也就是說,其輸出不是按照開始加入的順序,而 Dictionary遍歷輸出的順序,就是加入的順序,這點與 Hashtable不同。如果一定要排序 HashTable輸出,只能自己實現:

 // Hashtable sorting 
 
    System.Collections.ArrayList akeys =  new System.Collections.ArrayList(ht.Keys); // from Hashtable 
 
    akeys.Sort(); // Sort by leading letter 
 
foreach ( string skey in akeys)
     
{
         Console.Write(skey 
+  " : " );
         Console.WriteLine(ht[skey]);
     }

HashTable 與執行緒安全 

為了保證在多執行緒的情況下的執行緒同步訪問安全,微軟提供了自動執行緒同步的 HashTable:

如果 HashTable 要允許併發讀但只能一個執行緒寫 , 要這麼建立 HashTable 例項 :

 // Thread safe HashTable 
 
    System.Collections.Hashtable htSyn = System.Collections.Hashtable.Synchronized( newSystem.Collections.Hashtable());

這樣 , 如果有多個執行緒併發的企圖寫 HashTable 裡面的 item, 則同一時刻只能有一個執行緒寫 , 其餘阻塞 ; 對讀的執行緒則不受影響。

另外一種方法就是使用 lock 語句,但要 lock 的不是 HashTable ,而是其 SyncRoot ;雖然不推薦這種方法,但效果一樣的,因為原始碼就是這樣實現的 :

//Thread safe
private static System.Collections.Hashtable htCache = new System.Collections.Hashtable ();
public static void AccessCache ()
{
    lock ( htCache.SyncRoot )