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 voidTask1(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); } }
與預期結果不同
調整為執行緒同步的集合
每種資料型別都包含一個靜態的Synchronized方法,如
ArrayList list = ArrayList.Synchronized(new ArrayList(1000000));
調整後的結果
- IsSynchronized判斷集合是否為執行緒同步
- 其內部通過給SyncRoot屬性加鎖進行同步(即Monitor.Enter)
自己控制鎖
public class Demo8 { ArrayList list = newArrayList(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); } }
這樣的結果顯然好看點.內部實現是在Add方法中做鎖定.效果自然不是很好.
其他集合類也是類似的操作
泛型集合
可以看到原非泛型集合內部的執行緒同步集合,在每次操作均採用鎖操作,但我們並非每個操作都需要鎖,比如上面的2個執行緒操作.只需要2個鎖就可以了,但使用內部集合的話則需要鎖很多次,帶來了效能問題.在.net 2.0泛型集合中,內部不再支援執行緒同步的集合,即使內部實現了執行緒同步的集合如List<T>的實現也為開發出來,即把lock的這個操作轉嫁給開發者上面了.其實這樣反而可以讓我們更加了解執行緒同步的問題,如果真有需要的話,也可以自己實現一個了...
NET Framework 4 中的並行程式設計9---執行緒安全集合類
在.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<object, int> sharedDict = new ConcurrentDictionary<object, int>();
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 sampleSystem.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內部實現差不多,但前者無需裝箱拆箱操作,效率略高一點。
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 i in fruits.Keys)
{
Console.WriteLine( " key:{0} value:{1} " , i, fruits);
}
if (fruits.ContainsKey( 1 ))
{
Console.WriteLine( " contain this key. " );
}
ArrayList 是一維變長陣列,內部值為 object型別,效率一般:
System.Collections.ArrayList list = new System.Collections.ArrayList();
list.Add( 1 ); // object type
list.Add( 2 );
for ( int i = 0 ; i < list.Count; i ++ )
{
Console.WriteLine(list[i]);
}
HashTable是經過優化的,訪問下標的物件先雜湊過,所以內部是無序雜湊的,保證了高效率,也就是說,其輸出不是按照開始加入的順序,而 Dictionary遍歷輸出的順序,就是加入的順序,這點與 Hashtable不同。如果一定要排序 HashTable輸出,只能自己實現:
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 HashTableSystem.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 )