C# 集合-並發處理-鎖OR線程


  簡單的總結下對預防並發的理解:預防並發其實就是將並行執行修改為串行執行

   C#命名空間:System.Collenctions和System.Collenctions.Generic 中提供了很多列表、集合和數組。例如:List<T>集合,數組Int[],String[] ......,Dictory<T,T>字典等等。但是這些列表、集合和數組的線程都不是安全的,不能接受並發請求。下面通過一個例子來加以說明,如下:

class Program
    {
        private static object o = new object();
        private static List<Product> _Products { get; set; }
        /*  coder:天才卧龍  
         *  代碼中 創建三個並發線程 來操作_Products 集合
         *  System.Collections.Generic.List 這個列表在多個線程訪問下,不能保證是安全的線程,所以不能接受並發的請求,我們必須對ADD方法的執行進行串行化
         */
        static void Main(string[] args)
        {
            _Products = new List<Product>();
            /*創建任務 t1  t1 執行 數據集合添加操作*/
            Task t1 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });
            /*創建任務 t2  t2 執行 數據集合添加操作*/
            Task t2 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });
            /*創建任務 t3  t3 執行 數據集合添加操作*/
            Task t3 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });
            Task.WaitAll(t1, t2, t3);
            Console.WriteLine(_Products.Count);
            Console.ReadLine();
        }

        /*執行集合數據添加操作*/
        static void AddProducts()
        {
            Parallel.For(0, 1000, (i) =>
            {
                Product product = new Product();
                product.Name = "name" + i;
                product.Category = "Category" + i;
                product.SellPrice = i;
                _Products.Add(product);
            });

        }
    }

    class Product
    {
        public string Name { get; set; }
        public string Category { get; set; }
        public int SellPrice { get; set; }
    }

 

 

本例中,開辟了三個線程,通過循環向集合中添加數據,每個線程執行1000次(三個線程之間的操作是同時進行的,也是並行的),那么,理論上結果應該是3000。

   上文中我們講到: C#命名空間:System.Collenctions和System.Collenctions.Generic 下的列表,數組,集合並不能保證線程安全,並不能防止並發的發生。

   本例運行的結果也證明了上述結論的正確性,其結果如下:

   由此可見:C#命名空間:System.Collenctions和System.Collenctions.Generic 下的列表,數組,集合確實不能保證線程安全,確實不能預防並發。那么我們應當怎么解決上述問題呢?

   還好,自C#2.0以來,LOCK是一直存在的。使用LOCK(互斥鎖)是可以做到防止並發的,示例代碼如下:

 class Program
    {
        private static object o = new object();
        private static List<Product> _Products { get; set; }
        /*  coder:天才卧龍  
         *  代碼中 創建三個並發線程 來操作_Products 集合
         *  System.Collections.Generic.List 這個列表在多個線程訪問下,不能保證是安全的線程,所以不能接受並發的請求,我們必須對ADD方法的執行進行串行化
         */
        static void Main(string[] args)
        {
            _Products = new List<Product>();
            /*創建任務 t1  t1 執行 數據集合添加操作*/
            Task t1 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });
            /*創建任務 t2  t2 執行 數據集合添加操作*/
            Task t2 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });
            /*創建任務 t3  t3 執行 數據集合添加操作*/
            Task t3 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });
            Task.WaitAll(t1, t2, t3);
            Console.WriteLine(_Products.Count);
            Console.ReadLine();
        }

        /*執行集合數據添加操作*/
        static void AddProducts()
        {
            Parallel.For(0, 1000, (i) =>
               {
                   lock (o)
                   {

                       Product product = new Product();
                       product.Name = "name" + i;
                       product.Category = "Category" + i;
                       product.SellPrice = i;
                       _Products.Add(product);
                   }
               });
        }
    }

    class Product
    {
        public string Name { get; set; }
        public string Category { get; set; }
        public int SellPrice { get; set; }
    }

引入了Lock,運行結果也正常了,如下:

   但是鎖的引入,帶來了一定的開銷和性能的損耗,並降低了程序的擴展性,而且還會有死鎖的發生(雖說概率不大,但也不能不防啊),因此:使用LOCK進行並發編程顯然不太適用。

   還好,微軟一直在更新自己的東西:

   .NET Framework 4提供了新的線程安全和擴展的並發集合,它們能夠解決潛在的死鎖問題和競爭條件問題,因此在很多復雜的情形下它們能夠使得並行代碼更容易編寫,這些集合盡可能減少使用鎖的次數,從而使得在大部分情形下能夠優化為最佳性能,不會產生不必要的同步開銷。

   需要注意的是:在串行代碼中使用並發集合是沒有意義的,因為它們會增加無謂的開銷。

   在.NET Framework4.0以后的版本中提供了命名空間:System.Collections.Concurrent 來解決線程安全問題,通過這個命名空間,能訪問以下為並發做好了准備的集合。

   1.BlockingCollection 與經典的阻塞隊列數據結構類似,能夠適用於多個任務添加和刪除數據,提供阻塞和限界能力。

   2.ConcurrentBag 提供對象的線程安全的無序集合

   3.ConcurrentDictionary  提供可有多個線程同時訪問的鍵值對的線程安全集合

   4.ConcurrentQueue   提供線程安全的先進先出集合

   5.ConcurrentStack   提供線程安全的后進先出集合

   這些集合通過使用比較並交換和內存屏障等技術,避免使用典型的互斥重量級的鎖,從而保證線程安全和性能。

   ConcurrentQueue 

   ConcurrentQueue 是完全無鎖的,能夠支持並發的添加元素,先進先出。下面貼代碼,詳解見注釋:

class Program
    {
        private static object o = new object();
        /*定義 Queue*/
        private static Queue<Product> _Products { get; set; }
        private static ConcurrentQueue<Product> _ConcurrenProducts { get; set; }
        /*  coder:天才卧龍  
         *  代碼中 創建三個並發線程 來操作_Products 和 _ConcurrenProducts 集合,每次添加 10000 條數據 查看 一般隊列Queue 和 多線程安全下的隊列ConcurrentQueue 執行情況
         */
        static void Main(string[] args)
        {
            Thread.Sleep(1000);
            _Products = new Queue<Product>();
            Stopwatch swTask = new Stopwatch();//用於統計時間消耗的
            swTask.Start();

            /*創建任務 t1  t1 執行 數據集合添加操作*/
            Task t1 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });
            /*創建任務 t2  t2 執行 數據集合添加操作*/
            Task t2 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });
            /*創建任務 t3  t3 執行 數據集合添加操作*/
            Task t3 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });

            Task.WaitAll(t1, t2, t3);
            swTask.Stop();
            Console.WriteLine("List<Product> 當前數據量為:" + _Products.Count);
            Console.WriteLine("List<Product> 執行時間為:" + swTask.ElapsedMilliseconds);

            Thread.Sleep(1000);
            _ConcurrenProducts = new ConcurrentQueue<Product>();
            Stopwatch swTask1 = new Stopwatch();
            swTask1.Start();

            /*創建任務 tk1  tk1 執行 數據集合添加操作*/
            Task tk1 = Task.Factory.StartNew(() =>
            {
                AddConcurrenProducts();
            });
            /*創建任務 tk2  tk2 執行 數據集合添加操作*/
            Task tk2 = Task.Factory.StartNew(() =>
            {
                AddConcurrenProducts();
            });
            /*創建任務 tk3  tk3 執行 數據集合添加操作*/
            Task tk3 = Task.Factory.StartNew(() =>
            {
                AddConcurrenProducts();
            });

            Task.WaitAll(tk1, tk2, tk3);
            swTask1.Stop();
            Console.WriteLine("ConcurrentQueue<Product> 當前數據量為:" + _ConcurrenProducts.Count);
            Console.WriteLine("ConcurrentQueue<Product> 執行時間為:" + swTask1.ElapsedMilliseconds);
            Console.ReadLine();
        }

        /*執行集合數據添加操作*/

        /*執行集合數據添加操作*/
        static void AddProducts()
        {
            Parallel.For(0, 30000, (i) =>
            {
                Product product = new Product();
                product.Name = "name" + i;
                product.Category = "Category" + i;
                product.SellPrice = i;
                lock (o)
                {
                    _Products.Enqueue(product);
                }
            });

        }
        /*執行集合數據添加操作*/
        static void AddConcurrenProducts()
        {
            Parallel.For(0, 30000, (i) =>
            {
                Product product = new Product();
                product.Name = "name" + i;
                product.Category = "Category" + i;
                product.SellPrice = i;
                _ConcurrenProducts.Enqueue(product);
            });

        }
    }

    class Product
    {
        public string Name { get; set; }
        public string Category { get; set; }
        public int SellPrice { get; set; }
    }

 

 結果如下:

   

   從執行時間上來看,使用 ConcurrentQueue 相比 LOCK 明顯快了很多!

   1.BlockingCollection 與經典的阻塞隊列數據結構類似,能夠適用於多個任務添加和刪除數據,提供阻塞和限界能力。

   2.ConcurrentBag 提供對象的線程安全的無序集合

   3.ConcurrentDictionary  提供可有多個線程同時訪問的鍵值對的線程安全集合

   4.ConcurrentQueue   提供線程安全的先進先出集合

   5.ConcurrentStack   提供線程安全的后進先出集合

   上面的實例可以使用ConcurrentBag嗎?當然是可以的啦,因為:ConcurrentBag 和 ConcurrentQueue一樣,操作的對象都是集合,只不過方式不同罷了!同理:小虎斑們也可以嘗試使用 ConcurrentStack 在這里,我僅僅貼上使用ConcurrentBag的代碼,如下:

class Program
    {
        private static object o = new object();
        /*定義 Queue*/
        private static Queue<Product> _Products { get; set; }
        private static ConcurrentBag<Product> _ConcurrenProducts { get; set; }
        /*  coder:天才卧龍  
         *  代碼中 創建三個並發線程 來操作_Products 和 _ConcurrenProducts 集合,每次添加 10000 條數據 查看 一般隊列Queue 和 多線程安全下的隊列ConcurrentQueue 執行情況
         */
        static void Main(string[] args)
        {
            Thread.Sleep(1000);
            _Products = new Queue<Product>();
            Stopwatch swTask = new Stopwatch();//用於統計時間消耗的
            swTask.Start();

            /*創建任務 t1  t1 執行 數據集合添加操作*/
            Task t1 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });
            /*創建任務 t2  t2 執行 數據集合添加操作*/
            Task t2 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });
            /*創建任務 t3  t3 執行 數據集合添加操作*/
            Task t3 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });

            Task.WaitAll(t1, t2, t3);
            swTask.Stop();
            Console.WriteLine("List<Product> 當前數據量為:" + _Products.Count);
            Console.WriteLine("List<Product> 執行時間為:" + swTask.ElapsedMilliseconds);

            Thread.Sleep(1000);
            _ConcurrenProducts = new ConcurrentBag<Product>();
            Stopwatch swTask1 = new Stopwatch();
            swTask1.Start();

            /*創建任務 tk1  tk1 執行 數據集合添加操作*/
            Task tk1 = Task.Factory.StartNew(() =>
            {
                AddConcurrenProducts();
            });
            /*創建任務 tk2  tk2 執行 數據集合添加操作*/
            Task tk2 = Task.Factory.StartNew(() =>
            {
                AddConcurrenProducts();
            });
            /*創建任務 tk3  tk3 執行 數據集合添加操作*/
            Task tk3 = Task.Factory.StartNew(() =>
            {
                AddConcurrenProducts();
            });

            Task.WaitAll(tk1, tk2, tk3);
            swTask1.Stop();
            Console.WriteLine("ConcurrentQueue<Product> 當前數據量為:" + _ConcurrenProducts.Count);
            Console.WriteLine("ConcurrentBag<Product> 執行時間為:" + swTask1.ElapsedMilliseconds);
            Console.ReadLine();
        }

        /*執行集合數據添加操作*/

        /*執行集合數據添加操作*/
        static void AddProducts()
        {
            Parallel.For(0, 30000, (i) =>
            {
                Product product = new Product();
                product.Name = "name" + i;
                product.Category = "Category" + i;
                product.SellPrice = i;
                lock (o)
                {
                    _Products.Enqueue(product);
                }
            });

        }
        /*執行集合數據添加操作*/
        static void AddConcurrenProducts()
        {
            Parallel.For(0, 30000, (i) =>
            {
                Product product = new Product();
                product.Name = "name" + i;
                product.Category = "Category" + i;
                product.SellPrice = i;
                _ConcurrenProducts.Add(product);
            });

        }
    }

    class Product
    {
        public string Name { get; set; }
        public string Category { get; set; }
        public int SellPrice { get; set; }
    }

 

 執行結果如下:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM