c#集合类的线程安全

 

  1. QueueT 类
  2. 集合线程同步的问题
  3. 调整为线程同步的集合
  4. 自己控制锁
  5. 泛型集合
    1. NET Framework 4 中的并行编程9---线程安全集合类
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
评论:
0
浏览:
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<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 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 i 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 i = 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( 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 )

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值