C#的並發循環(for,foreach,parallel.for,parallel.foreach)對比


1.為了測試,我們創建一個10萬條數據的集合

 1 using System.Collections.Generic;
 2 
 3 namespace ParallelProcessing
 4 {
 5     class TestPerson
 6     {
 7         public string ID { get; set; }
 8         public string Name { get; set; }
 9 
10         public string Interest { get; set; }
11         public TestPerson(string id, string name, string interest)
12         {
13             this.ID = id;
14             this.Name = name;
15             this.Interest = interest;
16         }
17         public static List<TestPerson> CreateTestData()
18         {
19             List<TestPerson> testDataList = new List<TestPerson>();
20             for(int i = 1; i<=100001; i++)
21             {
22                 testDataList.Add(new TestPerson(i.ToString().PadLeft(6, '0'), "Name" + i, "Interest" + i));
23             }
24             return testDataList;
25         }
26     }
27 }

一個簡單的集合類,為了更貼切實際的項目。我們沒有沒有采用線程的等待代碼去測試這些循環方法,因為我想讓大家在自己run代碼的時候能發現並發的循環其實並不穩定。

 

2.測試主題方法:

 1 // 創建測試集
 2         static List<TestPerson> personData = TestPerson.CreateTestData();
 3         static System.IO.StreamWriter DefaultForFile = new System.IO.StreamWriter(@"C:\WorkSpace\ParallelProcessing\DefaultFor.txt", true);
 4         static System.IO.StreamWriter ParallelForFile = new System.IO.StreamWriter(@"C:\WorkSpace\ParallelProcessing\ParallelFor.txt", true);
 5         static void Main(string[] args)
 6         {
 7             // 當前用於計算循環語句執行時間
 8             Stopwatch stopWatch = new Stopwatch();
 9             stopWatch.Start();
10             // 執行普通的for循環
11             DefaultFor();
12             stopWatch.Stop();
13             long defalutForRunTime = stopWatch.ElapsedMilliseconds;
14             Console.WriteLine("DefaultFor run " + defalutForRunTime + " ms.");
15             stopWatch.Reset();
16             stopWatch.Start();
17             // 執行並發的for循環
18             ParallelFor();
19             stopWatch.Stop();
20             long parallelForRunTime = stopWatch.ElapsedMilliseconds;
21             Console.WriteLine("ParallelFor run " + parallelForRunTime + " ms.");
22             Console.WriteLine("ParallelFor/DefaultFor : " + (1-parallelForRunTime*1.0f / defalutForRunTime*1.0f) * 100 + "%.");
23             Console.ReadKey();
24         }
25         public static void DefaultFor()
26         {
27             List<TestPerson> newTestDatas = new List<TestPerson>();
28             for (int i = 0; i < personData.Count; i++)
29             {
30                 TestPerson tp = new(personData[i].ID, personData[i].Name, personData[i].Interest);
31                 newTestDatas.Add(tp);
32                 DefaultForFile.WriteLine(string.Format("迭代次數:{0};任務ID:{1};線程ID:{2}.", i, Task.CurrentId, Thread.CurrentThread.ManagedThreadId));
33             }
34             DefaultForFile.Close();
35         }
36         public static void ParallelFor()
37         {
38             ConcurrentBag<TestPerson> newTestDatas = new ConcurrentBag<TestPerson>();
39             ConcurrentBag<string> bag = new ConcurrentBag<string>();
40             Parallel.For(0, personData.Count, (i)=>
41             {
42                 newTestDatas.Add(new(personData[i].ID, personData[i].Name, personData[i].Interest));
43                 bag.Add(string.Format("迭代次數:{0};任務ID:{1};線程ID:{2}.", i, Task.CurrentId, Thread.CurrentThread.ManagedThreadId));
44             });
45             //Parallel.For(0, bag.Count, (j) => 
46             //{
47             //    ParallelForFile.WriteLine(bag.ToArray()[j]);
48             //});
49             //ParallelForFile.Close();
50             string[] msgArr = bag.ToArray();
51             for (int i = 0; i< msgArr.Length; i++)
52             {
53                 ParallelForFile.WriteLine(msgArr[i]);
54             }
55             ParallelForFile.Close();
56         }

3.測試結果:

第一次執行
DefaultFor run 234 ms.
ParallelFor run 217 ms.
ParallelFor/DefaultFor : 7.26496%.
第二次執行
DefaultFor run 277 ms.
ParallelFor run 243 ms.
ParallelFor/DefaultFor : 12.274366%.
第三次執行
DefaultFor run 237 ms.
ParallelFor run 178 ms.
ParallelFor/DefaultFor : 24.894518%.

總結:

1.是不是有了parallel.for循環就可以不使用for了?

   不是,比如這段代碼

            //Parallel.For(0, bag.Count, (j) => 
            //{
            //    ParallelForFile.WriteLine(bag.ToArray()[j]);
            //});
            //ParallelForFile.Close();
當我們需要給外部共同的資源(成員變量,外部DB,文件)賦值的時候用paralle.for就會很慢,因為多線程同時訪問外部實體,會產生阻塞和等待,它的效率比for要低。
2.paralle一定比普通的循環快嗎,它穩定嗎?
也不是。從測試結果我們可以看出parallel並不穩定,也就是說paralle可能每次執行的時間都不固定。我有一次確實看到使用paralle比使用普通的循環要慢。
它的快慢是啟動了多少線程決定的。
3.paralle有使用限制嗎?
有的。我們使用paralle的循環體可以是任何一個集合,但是如果給集合賦值的時候我們要使用線程安全的集合去裝載這些值。比如我用的是ConcurrentBag。也有專門封裝隊列,棧,Dictionary的集合。
官方有個說法很重要:
在對任何代碼(包括循環)進行並行化時,一個重要的目標是利用盡可能多的處理器,而不會過度並行化到並行處理的開銷使任何性能優勢消耗殆盡的程度。 在本特定示例中,只會對外部循環進行並行化,原因是不會在內部循環中執行太多工作。
少量工作和不良緩存影響的組合可能會導致嵌套並行循環的性能降低。 因此,僅並行化外部循環是在大多數系統上最大程度地發揮並發優勢的最佳方式。
4.可不可以paralle循環套paralle循環?
可以,但是不推薦。
5.paralle自動啟動多少線程?
我並不知道,通過我run的結果,有次最大26.
6.我在什么時候不能使用paralle?
從log可以看出,paralle的輸出結果是無序的。如果你需要得到有序的集合則必須在對新的集合進行排序。
迭代次數:20629;任務ID:2;線程ID:4.
迭代次數:20628;任務ID:2;線程ID:4.
迭代次數:20627;任務ID:2;線程ID:4.
迭代次數:20626;任務ID:2;線程ID:4.
迭代次數:20625;任務ID:2;線程ID:4.

 


 


免責聲明!

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



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