如何将此 foreach 代码转换为 Parallel.ForEach?

Posted

技术标签:

【中文标题】如何将此 foreach 代码转换为 Parallel.ForEach?【英文标题】:How can I convert this foreach code to Parallel.ForEach? 【发布时间】:2012-08-28 10:16:05 【问题描述】:

我对@9​​87654322@ 有点困惑。Parallel.ForEach 是什么,它到底有什么作用? 请不要引用任何 MSDN 链接。

这是一个简单的例子:

string[] lines = File.ReadAllLines(txtProxyListPath.Text);
List<string> list_lines = new List<string>(lines);

foreach (string line in list_lines)

    //My Stuff

我怎样才能用Parallel.ForEach重写这个例子?

【问题讨论】:

这里可能已经回答了***.com/questions/3789998/… @UjjwalManandhar 这实际上完全不同,因为它询问Parallel 类和使用 PLINQ 之间的区别。 其他人已经回答了如何重写。那么它有什么作用呢?它对集合中的每个项目执行“操作”,就像普通的 foreach 一样。不同的是,并行版本可以同时做很多“动作”。在大多数情况下(取决于运行代码的计算机、它的繁忙程度以及其他因素)它会更快,这是最重要的优势。请注意,当您并行执行时,您无法知道处理项目的顺序。使用通常的(串行)foreach,您可以保证首先出现lines[0],然后是lines[1],依此类推。 @JeppeStigNielsen 它不会总是更快,因为并行处理会产生大量开销。这取决于您正在迭代的集合的大小和其中的操作。正确的做法是实际测量使用 Parallel.ForEach() 和使用 foreach() 之间的差异。很多时候,普通的 foreach() 更快。 @DaveBlack 当然。在每种情况下,都必须衡量它是更快还是更慢。我只是想概括地描述并行化。 【参考方案1】:

Foreach 循环:

迭代按顺序进行,一个接一个 foreach 循环从单个线程运行。 .NET 的每个框架中都定义了 foreach 循环 slow 进程的执行可能会较慢,因为它们是连续运行的 进程 2 在 1 完成之前无法启动。流程 3 无法启动,直到 2 和 1 完成... 快速进程的执行可以更快,因为没有线程开销

Parallel.ForEach:

以并行方式执行。 Parallel.ForEach 使用多个线程。 Parallel.ForEach 在 .Net 4.0 及更高版本的框架中定义。 进程的执行可以更快,因为它们可以并行运行 进程 1、2 和 3可能同时运行(请参阅下面的示例中的重用线程) 由于额外的线程开销,快速进程的执行可能较慢

下面的例子清楚地展示了传统foreach循环与

的区别

Parallel.ForEach() 示例

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace ParallelForEachExample

    class Program
    
        static void Main()
        
            string[] colors = 
                                  "1. Red",
                                  "2. Green",
                                  "3. Blue",
                                  "4. Yellow",
                                  "5. White",
                                  "6. Black",
                                  "7. Violet",
                                  "8. Brown",
                                  "9. Orange",
                                  "10. Pink"
                              ;
            Console.WriteLine("Traditional foreach loop\n");
            //start the stopwatch for "for" loop
            var sw = Stopwatch.StartNew();
            foreach (string color in colors)
            
                Console.WriteLine("0, Thread Id= 1", color, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(10);
            
            Console.WriteLine("foreach loop execution time = 0 seconds\n", sw.Elapsed.TotalSeconds);
            Console.WriteLine("Using Parallel.ForEach");
            //start the stopwatch for "Parallel.ForEach"
             sw = Stopwatch.StartNew();
            Parallel.ForEach(colors, color =>
            
                Console.WriteLine("0, Thread Id= 1", color, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(10);
            
            );
            Console.WriteLine("Parallel.ForEach() execution time = 0 seconds", sw.Elapsed.TotalSeconds);
            Console.Read();
        
    

输出

Traditional foreach loop
1. Red, Thread Id= 10
2. Green, Thread Id= 10
3. Blue, Thread Id= 10
4. Yellow, Thread Id= 10
5. White, Thread Id= 10
6. Black, Thread Id= 10
7. Violet, Thread Id= 10
8. Brown, Thread Id= 10
9. Orange, Thread Id= 10
10. Pink, Thread Id= 10
foreach loop execution time = 0.1054376 seconds

使用 Parallel.ForEach 示例

1. Red, Thread Id= 10
3. Blue, Thread Id= 11
4. Yellow, Thread Id= 11
2. Green, Thread Id= 10
5. White, Thread Id= 12
7. Violet, Thread Id= 14
9. Orange, Thread Id= 13
6. Black, Thread Id= 11
8. Brown, Thread Id= 10
10. Pink, Thread Id= 12
Parallel.ForEach() execution time = 0.055976 seconds

【讨论】:

我不太同意您的“声称”,即 Parallel.ForEach (总是)更快。这实际上取决于循环内部操作的繁重程度。这可能值得也可能不值得引入并行性的开销。 好吧,每个线程的并行意味着设置单独的线程来执行循环体中的代码。尽管 .NET 确实具有执行此操作的有效机制,但这是相当大的开销。因此,如果您只需要一个简单的操作(例如求和或乘法),并行 foreach 应该不会更快。 @Jignesh 这甚至不是很好的测量示例,所以我根本不会提到这个。删除“Thread.Sleep(10);”从每个循环体中再试一次。 @Martao 是对的,问题在于对象锁定开销,其中并行方法可能比顺序方法更长。 @stenly 我认为睡眠正是它成为示例的原因。您不会使用具有快速单次迭代的 PFE(正如 Martao 解释的那样) - 所以这个答案使迭代变慢,并且突出了 PFE 的(正确)优势。我同意虽然这需要在答案中解释,但粗体的“总是更快”非常具有误导性。【参考方案2】:
string[] lines = File.ReadAllLines(txtProxyListPath.Text);
List<string> list_lines = new List<string>(lines);
Parallel.ForEach(list_lines, line =>

    //Your stuff
);

【讨论】:

只是想指出这一点(对于 OP 来说更多),以免误以为它只适用于 List&lt;T&gt; ;) 感谢您的关注和回答。我在代码中使用了 List ,因为使用 HASH 列表删除了重复项。使用常规数组,我们无法轻松删除重复项:)。 我很困惑这个答案被标记为正确答案,因为没有对原始帖子问题“什么是 Parallel.ForEach 以及它到底做了什么?”的解释......跨度> @fosb 问题是问题标题已被编辑以完全改变含义......所以这个答案不再有意义。话虽如此,这仍然是一个糟糕的答案【参考方案3】:
string[] lines = File.ReadAllLines(txtProxyListPath.Text);

// No need for the list
// List<string> list_lines = new List<string>(lines); 

Parallel.ForEach(lines, line =>

    //My Stuff
);

这将导致在循环内并行解析行。如果您想要更详细、更少“面向参考”的 Parallel 类介绍,我写了一个关于 TPL 的系列文章,其中包括 section on Parallel.ForEach。

【讨论】:

【参考方案4】:

对于大文件,请使用以下代码(您的内存消耗较少)

Parallel.ForEach(File.ReadLines(txtProxyListPath.Text), line => 
    //Your stuff
);

【讨论】:

【参考方案5】:

这些行对我有用。

string[] lines = File.ReadAllLines(txtProxyListPath.Text);
var options = new ParallelOptions  MaxDegreeOfParallelism = Environment.ProcessorCount * 10 ;
Parallel.ForEach(lines , options, (item) =>

 //My Stuff
);

【讨论】:

【参考方案6】:

我想补充一下并行选项。如果您没有提到它,默认情况下所有 RAM 都将用于此,这可能会给您带来生产问题。所以最好在代码中添加最大并行度。

Parallel.ForEach(list_lines, new ParallelOptions  MaxDegreeOfParallelism = 2 , line =>

    
);

【讨论】:

以上是关于如何将此 foreach 代码转换为 Parallel.ForEach?的主要内容,如果未能解决你的问题,请参考以下文章

如何将此 NSClassFromString 代码转换为 Swift?

如何将此代码转换为 PyQt5?

如何将此项目转换为代码?

如何将此代码转换为 for 循环格式?

如何将此.XAML代码转换为C#代码

如何将此代码从 Dijkstra 转换为 Astar?