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.