zoukankan      html  css  js  c++  java
  • 请设计一个线程安全的集合(可以直接继承自List<T>)

    1.对非线程安全类List的一些总结(首先了解)
    描述场景:一个项目的一个功能点,需要从接口接受返回数据,并对返回的数据进行一些业务处理,处理完成之后,添加到一个List中,然后在View中循环这个List,展示所有的数据。
    每次从接口中取回的数据量不等,最多会有上百条。虽说上百条也不算多,但是每条数据都要经过一系列的业务处理,感觉这样也挺耗时的,于是考虑使用Parallel.Foreach来进行并行处理。
    项目完成之后,对比了一下并行和非并行的情况,发现并行之后并没有提高多少效能,倒是遇到了一些比较怪异的问题。

    出现的问题:Parallel.Foreach 中对List执行Add操作之后,List的Count有时候并不是执行并行的操作的执行次数,而且List中会有Item为null的情况。

    分析:因为List不是线程安全的类,在多线程情况下就会导致一些不可预知的情况。微软开放了List源码,地址:http://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,cf7f4095e4de7646
    从源码中我们可以看到List是通过一个Array来进行处理的,如果初始没有对List设置容量,List容量将为0,如果此时使用Add添加新项的时候,就会给List设置一个初始容量(初始值为4)。使用Add添加新项的时候,如果已经达到容量最大值,List会自动扩充容量的值,扩充后的容量的值为原来既有项目数量的2倍(其实也就是原来容量的2倍)。

    我们把Add方法和扩容方法摘抄如下:

    public void Add(T item) {
                if (_size == _items.Length) EnsureCapacity(_size + 1);
                _items[_size++] = item;
                _version++;
            }
    
    private void EnsureCapacity(int min) {
                if (_items.Length < min) {
                    int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2;
                    // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
                    // Note that this check works even when _items.Length overflowed thanks to the (uint) cast
                    if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
                    if (newCapacity < min) newCapacity = min;
                    Capacity = newCapacity;
                }
            }
    

    了解了List内部内部扩容情况之后,下面就以上两个问题进行分析:

    1、 List中的Item数量比预期的少。

    导致这个问题的原因其实还是挺明显的。当两个线程(ThreadA和TreadB),同时调用Add方法添加不同的值的时候,如果此时ThreadA和ThreadB获取到的size相同,就会出现下面这种情况:

    ThreadA:List[size] = A;

    ThreadB:List[size] = B;

    这种情况下,在size这个位置只会有一个ThreadB设置的值,ThreadA设置的值将会被替换掉,这也就是造成Item数量比预期少的原因。

    2、 List中的Item有null。

    其实和上面类似,看Add中的代码:

    _items[_size++] = item;
    我们改变一下,变成:

    (1)_size = _size+1;

    (2)Items[_size] = item;

    如果ThreadA执行完(1)之后ThreadB获取到新的_size也执行了(1)那此时_size就相当于是加2了,所以_size+1索引位置的项就是T的默认值了(值类型会值类型的默认值,引用类型为null)。这样就能解释为什么会出现null的原因了。

    解决方案:其实这两个问题完全就是同一个问题,只不过表象不同而已。最终解决方案很简单,要么自己加锁,要么使用线程安全的ConcurrentBag
    1.手写一个类继承自List,给每个T加上lock锁,参照代码如下:

    myList.Add(object1);
    
    //修改为
    public static object lockData=new  object();
     
    lock(lockData)
    {
        myList.Add(object1)
    }
    
    

    2.当排序并不重要时,包可用于存储对象,而与集不同,包支持重复项。 ConcurrentBag 是一个线程安全包实现,适用于同一线程将生成和使用存储在包中的数据的情况。
    ConcurrentBag 接受 null 作为引用类型的有效值。
    ConcurrentBag使用多种机制来最小化同步的需求。 例如,它为访问它的每个线程维护一个本地队列,并且在某些条件下,线程能够以无锁的方式访问其本地队列,很少或没有争用。 因此,虽然ConcurrentBag有时需要锁定,但对于某些并发场景(例如,许多以相同速率产生和使用的线程),它是一个非常有效的集合。

    https://docs.microsoft.com/zh-cn/dotnet/api/system.collections.concurrent.concurrentbag-1?view=netframework-4.7.2

  • 相关阅读:
    sql server 字符串转成日期格式
    删除重复的数据
    sql server 去掉前后空格
    取出分组后每组的第一条记录(不用group by)按时间排序
    选中datagridview要选择的行
    sql server 跨表修改
    winfrom 打印表单
    .net导入Excel数据遇到问题(SQL Server 阻止了对组件 'Ad Hoc Distributed Queries' 的 STATEMENT 'OpenRowset/OpenDataso) .
    ComboBox绑定数据源后再添加一条记录
    机器学习 | 一个基于机器学习的简单小实践:波斯顿房价预测分析
  • 原文地址:https://www.cnblogs.com/ButterflyEffect/p/11945578.html
Copyright © 2011-2022 走看看