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

时间:2022-09-03 18:35:54

Queue<T>

MSDN的说法

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

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

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


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

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


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

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

与预期结果不同

调整为线程同步的集合

每种数据类型都包含一个静态的Synchronized方法,如

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

调整后的结果

c#集合类的线程安全(整理) 
以下为注意点:

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

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

这样的结果显然好看点.内部实现是在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的应用场合有:做对象缓存,树递归算法的替代,和各种需提升效率的场合。

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

c#集合类的线程安全(整理) 
c#集合类的线程安全(整理)     
//  traversal 2: 
c#集合类的线程安全(整理) 
    System.Collections.IDictionaryEnumerator d  =  ht.GetEnumerator();
c#集合类的线程安全(整理)     
while  (d.MoveNext())
c#集合类的线程安全(整理)     
{
c#集合类的线程安全(整理)         Console.WriteLine(
 " key:{0} value:{1} " , d.Entry.Key, d.Entry.Value);
c#集合类的线程安全(整理)     }
 

c#集合类的线程安全(整理) 
c#集合类的线程安全(整理)     
//  Clear items 
c#集合类的线程安全(整理) 
    ht.Clear();


Dictionary 和 HashTable内部实现差不多,但前者无需装箱拆箱操作,效率略高一点。

c#集合类的线程安全(整理)       //  Dictionary sample 
c#集合类的线程安全(整理) 
    System.Collections.Generic.Dictionary  <  int  string  >  fruits  =   
c#集合类的线程安全(整理)         
new  System.Collections.Generic.Dictionary  <  int  string  >  ();
c#集合类的线程安全(整理) 
c#集合类的线程安全(整理)     fruits.Add(
  1  "  apple  "  );
c#集合类的线程安全(整理)     fruits.Add(
  2  "  banana  "  );
c#集合类的线程安全(整理)     fruits.Add(
  3  "  orange  "  );
c#集合类的线程安全(整理) 
c#集合类的线程安全(整理)     
foreach  (  int  in  fruits.Keys)
c#集合类的线程安全(整理)     
{
c#集合类的线程安全(整理)         Console.WriteLine(
 " key:{0} value:{1} " , i, fruits);
c#集合类的线程安全(整理)     }
 

c#集合类的线程安全(整理) 
c#集合类的线程安全(整理)     
if  (fruits.ContainsKey(  1  ))
c#集合类的线程安全(整理)     
{
c#集合类的线程安全(整理)         Console.WriteLine(
 " contain this key. " );
c#集合类的线程安全(整理)     }

 

ArrayList 是一维变长数组,内部值为 object类型,效率一般:

 

c#集合类的线程安全(整理)       //  ArrayList 
c#集合类的线程安全(整理) 
    System.Collections.ArrayList list  =     new  System.Collections.ArrayList();
c#集合类的线程安全(整理)     list.Add(
  1  );  //  object type 
c#集合类的线程安全(整理) 
    list.Add(  2  );
c#集合类的线程安全(整理)     
for  (  int  =     0  ; i  <  list.Count; i  ++  )
c#集合类的线程安全(整理)     
{
c#集合类的线程安全(整理)         Console.WriteLine(list[i]);
c#集合类的线程安全(整理)     }



HashTable是经过优化的,访问下标的对象先散列过,所以内部是无序散列的,保证了高效率,也就是说,其输出不是按照开始加入的顺序,而 Dictionary遍历输出的顺序,就是加入的顺序,这点与 Hashtable不同。如果一定要排序 HashTable输出,只能自己实现:

c#集合类的线程安全(整理)       //  Hashtable sorting 
c#集合类的线程安全(整理) 
    System.Collections.ArrayList akeys  =     new  System.Collections.ArrayList(ht.Keys);  //  from Hashtable 
c#集合类的线程安全(整理) 
    akeys.Sort();  //  Sort by leading letter 
c#集合类的线程安全(整理) 
     foreach  (  string  skey  in  akeys)
c#集合类的线程安全(整理)     
{
c#集合类的线程安全(整理)         Console.Write(skey 
+   " : " );
c#集合类的线程安全(整理)         Console.WriteLine(ht[skey]);
c#集合类的线程安全(整理)     }

 

HashTable 与线程安全 

为了保证在多线程的情况下的线程同步访问安全,微软提供了自动线程同步的 HashTable:

如果 HashTable 要允许并发读但只能一个线程写 , 要这么创建 HashTable 实例 :

c#集合类的线程安全(整理)       //  Thread safe HashTable 
c#集合类的线程安全(整理) 
    System.Collections.Hashtable htSyn  =  System.Collections.Hashtable.Synchronized(  new System.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 )