c#集合类的线程安全(整理)

来源:互联网 发布:bing 网络是什么意思 编辑:程序博客网 时间:2024/05/17 04:55

Queue<T>

MSDN的说法
线程安全

此类型的公共静态(在 Visual Basic 中为 Shared)成员是线程安全的。但不能保证任何实例成员是线程安全的。

只要不修改该集合,Queue<T> 就可以同时支持多个阅读器。即便如此,从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。

若要确保枚举过程中的线程安全,可以在整个枚举过程中锁定集合。若要允许多个线程访问集合以进行读写操作,则必须实现自己的同步。


.NET在4.0里面提供了专门的并行类,来弥补相关集合类的线程安全性。
System.Collections.Concurrent:

System.Collections.Concurrent命名空间提供多个线程安全集合类。当有多个线程并发访问集合时,应使用这些类代替 System.CollectionsSystem.Collections.Generic 命名空间中的对应类型。


 类说明公共类BlockingCollection<T>为实现 IProducerConsumerCollection<T> 的线程安全集合提供阻塞和限制功能。公共类ConcurrentBag<T>表示对象的线程安全的无序集合。公共类ConcurrentDictionary<TKey, TValue>表示可由多个线程同时访问的键值对的线程安全集合。公共类ConcurrentQueue<T>表示线程安全的先进先出 (FIFO) 集合。公共类ConcurrentStack<T>表示线程安全的后进先出 (LIFO) 集合。公共类OrderablePartitioner<TSource>表示将一个可排序数据源拆分成多个分区的特定方式。公共类Partitioner提供针对数组、列表和可枚举项的常见分区策略。公共类Partitioner<TSource>表示将一个数据源拆分成多个分区的特定方式。
接口

 接口说明公共接口IProducerConsumerCollection<T>定义供制造者/使用者用来操作线程安全集合的方法。此接口提供一个统一的表示(为生产者/消费者集合),从而更高级别抽象如System.Collections.Concurrent.BlockingCollection<T> 可以使用集合作为基础的存储机制。
这里包含,字典类,队列和栈,对应的相关集合均不是安全的。
============================================================================
微软提供了一种线程安全的扩展手段:

即位于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方法中做锁定.效果自然不是很好.

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

参考: 
http://www.cnblogs.com/Mainz/archive/2008/04/06/CSharp_HashTable_Dictionary_ArrayList_Threadsafe.html

泛型集合

可以看到原非泛型集合内部的线程同步集合,在每次操作均采用锁操作,但我们并非每个操作都需要锁,比如上面的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 )