多线程环境下,一个List<T>集合,不对进行它删除和修改操作,只对它做添加和读取操作.
譬如:fn1,fn2,fn3这三个函数对这个集合进行添加操作,fn4,fn5这两个函数对它进行读取操作,这五个函数并行运行,代码中不用lock这样会不会有问题?

解决方案 »

  1.   

    用parral.linq
    并行查询,不用管锁
      

  2.   

    只要不修改该集合,List<T> 就可以同时支持多个阅读器。 从头到尾对一个集合进行枚举在本质上不是一个线程安全的过程。 在枚举与一个或多个写访问竞争的罕见情况下,确保线程安全的唯一方法是在整个枚举期间锁定集合。 若要允许多个线程访问集合以进行读写操作,则必须实现自己的同步。
      

  3.   

    添加操作就是改变集合.参考MSDN:
    http://msdn.microsoft.com/zh-cn/library/6sh2ey19(v=vs.95).aspx最后一段:
    线程安全:此类型的公共静态(在 Visual Basic 中为 Shared)成员是线程安全的。但不保证所有实例成员都是线程安全的。只要不修改该集合,List<T> 就可以同时支持多个阅读器。枚举整个集合本质上不是一个线程安全的过程。在枚举与一个或多个写访问竞争的罕见情况下,确保线程安全的唯一方法是在整个枚举期间锁定集合。若允许多个线程对集合执行读写操作,您必须实现自己的同步。
      

  4.   

    还是没有深刻理解为什么不行,我不删除集合中的元素,即使一些函数往集合里添加元素,我读取集合的话,也不会报什么超出索引之类的错误,最多在同一时刻没有读取到最新元素
     读取操作用下面代码: for(int i=0;i<items.Count;++i)
       {
    Console.WriteLine(items[i]);
       }
      有谁能指出我理解上的漏洞?
      

  5.   

    "枚举整个集合本质上不是一个线程安全的过程。在枚举与一个或多个写访问竞争的罕见情况下,确保线程安全的唯一方法是在整个枚举期间锁定集合。若允许多个线程对集合执行读写操作,您必须实现自己的同步。"看懂伐?
    这种多线程读取的同一集合中的内容不是线程安全的,如果不加锁就会造成问题.你给出的代码不能解决在另外一个线程中Insert了元素的问题,不能保证读取的是正确结果,这就是线程不安全.不能保证读取的结果是正确的,这种代码能引入到项目中?例如
    线程1在枚举 for(int i=0;i<items.Count;++i){... };
    线程2在Insert: items.insert(10,XXX);能保证线程1枚举第十个元素的时候是什么值?如果不加锁,不能保证,可能是原来的第十个元素,也可能是后来Insert的.
      

  6.   


    对于多线程访问集合应该使用加锁机制,msdn上说了这种操作不是现场安全的,像你上面的代码 如果只针对添加和读取而且添加使用<List>.add() 方法应该能得到正确的结果,当时如果使用<List>.insert() 就会有问题。
      

  7.   

    谢谢大家恩,我只用到<List>.add()方法,不会用到<List>.insert()方法.我其实是想确定下在这个特定的条件下不加锁是不会有问题的吧?7楼的观点是不会有问题的.6楼的,即使我用到了<List>.insert()方法, 在插入并且并行发生读操作的时候,其实我对哪个位置是哪个元素不在意的,我在意的是读的时候插入操作不报错,并且能读到所有元素.
      

  8.   

    "我在意的是读的时候插入操作不报错,并且能读到所有元素"完全不懂了,一个持续递增的items?每次都循环这个items的全部元素?没有任何remove,delete,clear语句?这个需求很奇怪.
      

  9.   

    实际的情境是这样的,用多线程要从一个很大的数据源中获取数据到List<T>中,在获取的过程中在界面(C/S架构)上显示获取的进度.如每个线程获取1000个数据加入到List<T>中,界面上有个GridView,它采取虚拟模式从List读取数据,类似加载大数据量时当用户可以通过gridView的纵向滚动条浏览数据,数据还在不停地加载并且纵向滚动条也在不停变小的效果.不知道这样的需求合理不?
      

  10.   

    "类似加载大数据量时当用户可以通过gridView的纵向滚动条浏览数据,数据还在不停地加载并且纵向滚动条也在不停变小的效果."给你段代码,你就知道了,如果不加锁,读取的数据是不正确/不正常的
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;namespace MultiThread
    {
        class Program
        {
            static List<int> Itmes = new List<int>();
            static int iCount = 0;
            static void Main(string[] args)
            {
                Thread th1 = new Thread(new ThreadStart(Add));
                Thread th2 = new Thread(new ThreadStart(Add));
                Thread th3 = new Thread(new ThreadStart(Show));
                th1.Start();
                th2.Start();
                th3.Start();
            }        static void Add()
            {
                while (Itmes.Count < 1000000)
                {
                    for (int i = iCount; i < iCount + 1000; i++)
                    {
                        Itmes.Add(i);
                        Thread.Sleep(50);
                    }
                    iCount = iCount + 1000;
                }
                Thread.CurrentThread.Abort();
            }        static void Show()
            {
                while (Itmes.Count < 1000000)
                {
                    for (int i = iCount; i < Itmes.Count; i++)
                    {
                        Console.WriteLine(Itmes[i].ToString());
                    }
                }
                Thread.CurrentThread.Abort();
            }
        }
    }
      

  11.   

    你举的例子有点不严谨,你的程序有两个共享资源,一个是Itmes,一个是iCount,你的程序对iCount变量在多线程没加锁的情况下又读又写(主要是修改它的值),肯定是会出错的(我这里多出运行后的Itmes.Count属性值不一致,没出现程序crash掉),不符合我的问题的前提条件.我的前提条件是:只有一个共享资源,就是你程序中的Itmes对象.在不加锁的情况下对这个Items对象只做添加操作(先不考虑Insert操作,只是Add操作)时并行读取,不存在对其它的共享变量进行操作.以下是我对你例子的修改 static List<int> Itmes = new List<int>();
            //static int iCount = 0;
            
            static void Main(string[] args)
            {
                Thread th1 = new Thread(new ThreadStart(Add));
                Thread th2 = new Thread(new ThreadStart(Add));
                Thread th3 = new Thread(new ThreadStart(Show));            th1.IsBackground = true;
                th2.IsBackground = true;
                th3.IsBackground = true;            th1.Start();
                th2.Start();
                th3.Start();        
               // Console.WriteLine("按回车键退出程序");
                Console.ReadLine();
                //th1和th2结束后,这时的Items.Count属性值是2000000,不会出现不一致的情况
            }        static void Add()
            {
                //while (Itmes.Count < 1000000)
                //{
                //    for (int i = iCount; i < iCount + 1000; i++)
                //    {
                //        Itmes.Add(i);
                //       // Thread.Sleep(50);
                //    }
                //    iCount = iCount + 1000;
                //}
                //Thread.CurrentThread.Abort();            Random r = new Random();
                for (int i = 0; i < 100000; ++i)
                {
                    Itmes.Add(r.Next(int.MaxValue));
                    Thread.Sleep(20);
                }
            }        static void Show()
            {
                //while (Itmes.Count < 1000000)
                //{
                //    for (int i = iCount; i < Itmes.Count; i++)
                //    {
                //        Console.WriteLine(Itmes[i].ToString());
                //    }
                //}
                //Thread.CurrentThread.Abort();            while(true)
                for (int i = 0; i < Itmes.Count; ++i)
                {
                    Console.WriteLine(Itmes[i]);
                }
            }
      

  12.   

    你都没懂我的那个例子的意思.         while(true)
                for (int i = 0; i < Itmes.Count; ++i)
                {
                    Console.WriteLine(Itmes[i]);
                }
    你需要每次都在从 0 ~ Items.Count 进行显示.?如果是,我无话可说了,再讨论下去也没有意义了.
      

  13.   

    只做Add,不Insert的话,通过下标访问可以(你可能无法控制将哪些元素对应到哪些下标),但不要用到Linq的Last()之类的扩展方法。lz看一下ObservableCollection<T> ,比较适合做数据绑定,似乎同你的需求比较类似