C#並行,多執行緒程式設計並行集合和PLINQ的例項講解

Nincems發表於2019-01-09

並行演算法的出現,隨之而產生的也曾有了並行集合,及執行緒安全集合;微軟向的也算周到,沒有忘記linq,也推出了linq的並行版本,plinq - Parallel Linq。

一,並行集合 - 執行緒安全集合

  平行計算使用的多個執行緒同時進行計算,所以要控制每個執行緒對資源的訪問,我們先來看一下平時常用的列表集合,在平行計算下的表現,新建一個控制檯應用程式,新增一個PEnumerable類(當然你也直接寫到主方法裡面測試,建議分開寫),寫如下方法:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Concurrent;

namespace ThreadPool
{
    public class PEnumerable
   {
      public static void ListWithParallel()
      {
         List<int> list = new List<int>();
         Parallel.For(0, 10000, item =>
         {
            list.Add(item);
         });
         Console.WriteLine("List's count is {0}",list.Count());
      }
   }
}

看到結果中顯示的5851,但是我們迴圈的是10000次啊!怎麼結果不對呢?這是因為名單是非執行緒安全集合,意思就是說所有的執行緒都可以修改他的值。

下面我們來看下並行集合 - 執行緒安全集合,在System.Collections.Concurrent名稱空間中,首先來看一下ConcurrentBag泛型集合,其用法和列表類似,先來寫個方法測試一下:

public static void ConcurrentBagWithPallel()
  {
     ConcurrentBag<int> list = new ConcurrentBag<int>();
     Parallel.For(0, 10000, item =>
     {
        list.Add(item);
     });
     Console.WriteLine("ConcurrentBag's count is {0}", list.Count());
  }

可以看到,ConcurrentBag集合的結果是正確的下面我們修改程式碼看看ConcurrentBag裡面的資料到底是怎麼存放的,修改程式碼如下:

public static void ConcurrentBagWithPallel()
  {
     ConcurrentBag<int> list = new ConcurrentBag<int>();
     Parallel.For(0, 10000, item =>
     {
        list.Add(item);
     });
     Console.WriteLine("ConcurrentBag's count is {0}", list.Count());
     int n = 0;
     foreach(int i in list)
     {
        if (n > 10)
           break;
        n++;
        Console.WriteLine("Item[{0}] = {1}",n,i);
     }
     Console.WriteLine("ConcurrentBag's max item is {0}", list.Max());

  }

可以看到,ConcurrentBag中的資料並不是按照順序排列的,順序是亂的,隨機的。我們平時使用的最大,首先,最後等LINQ方法都還有。其時分類似可列舉的用法,大家可以參考微軟的MSDN瞭解它的具體用法。

關於執行緒安全的集合還有很多,和我們平時用的集合都差不多,比如類似字典的ConcurrentDictionary,還有ConcurrentStack,ConcurrentQueue等。

二,並行Linq的用法及效能

1,進行AsParallel

前面瞭解了並行的對於和的foreach,今天就來看一下的LINQ的並行版本是怎麼樣吧為了測試,我們新增一個自定義類,程式碼如下?

public class Custom

{ public string Name { get; set; } public int Age { get; set; } public string Address { get; set; } }

寫如下測試程式碼:

public static void TestPLinq()
  {
     Stopwatch sw = new Stopwatch();
     List<custom> customs = new List<custom>();
     for (int i = 0; i < 2000000; i++)
     {
        customs.Add(new Custom() { Name = "Jack", Age = 21, Address = "NewYork" });
        customs.Add(new Custom() { Name = "Jime", Age = 26, Address = "China" });
        customs.Add(new Custom() { Name = "Tina", Age = 29, Address = "ShangHai" });
        customs.Add(new Custom() { Name = "Luo", Age = 30, Address = "Beijing" });
        customs.Add(new Custom() { Name = "Wang", Age = 60, Address = "Guangdong" });
        customs.Add(new Custom() { Name = "Feng", Age = 25, Address = "YunNan" });
     }
     sw.Start();
     var result = customs.Where<custom>(c => c.Age > 26).ToList();
     sw.Stop();
     Console.WriteLine("Linq time is {0}.",sw.ElapsedMilliseconds);
     sw.Restart();
     sw.Start();
     var result2 = customs.AsParallel().Where<custom>(c => c.Age > 26).ToList();
     sw.Stop();
     Console.WriteLine("Parallel Linq time is {0}.", sw.ElapsedMilliseconds);
  }

其實也就是加了一個進行AsParallel()方法,下面來看下執行結果:

public static void OrderByTest()
  {
     Stopwatch stopWatch = new Stopwatch();
     List<custom> customs = new List<custom>();
     for (int i = 0; i < 2000000; i++)
     {
        customs.Add(new Custom() { Name = "Jack", Age = 21, Address = "NewYork" });
        customs.Add(new Custom() { Name = "Jime", Age = 26, Address = "China" });
        customs.Add(new Custom() { Name = "Tina", Age = 29, Address = "ShangHai" });
        customs.Add(new Custom() { Name = "Luo", Age = 30, Address = "Beijing" });
        customs.Add(new Custom() { Name = "Wang", Age = 60, Address = "Guangdong" });
        customs.Add(new Custom() { Name = "Feng", Age = 25, Address = "YunNan" });
     }
     stopWatch.Restart();
     var groupByAge = customs.GroupBy(item => item.Age).ToList();
     foreach (var item in groupByAge)
     {
        Console.WriteLine("Age={0},count = {1}", item.Key, item.Count());
     }
     stopWatch.Stop();
     Console.WriteLine("Linq group by time is: " + stopWatch.ElapsedMilliseconds);
     stopWatch.Restart();
     var lookupList = customs.ToLookup(i => i.Age);
     foreach (var item in lookupList)
     {
        Console.WriteLine("LookUP:Age={0},count = {1}", item.Key, item.Count());
     }
     stopWatch.Stop();
     Console.WriteLine("LookUp group by time is: " + stopWatch.ElapsedMilliseconds);
  }

執行結果如下:

enter image description here ToLookup方法是將集合轉換成一個只讀集合,所以在大資料量分組時效能優於名單。大家可以查閱相關資料,這裡由於篇幅問題,不再細說。

相關文章