Brief introduction
When it's time to optimize for multicore machines, it's a good idea to check if your program has processing to split up for parallel processing. (For example, there is a huge collection of data, where elements require a time-consuming calculation that is independent of each other).
Parallel.ForEach and PLINQ are available in the. NET Framework 4 to help us with parallel processing, and this article explores the differences between the two and the scenarios that apply.
Parallel.ForEach
Parallel.ForEach is a multi-threaded implementation of ForEach, and they all have the ability to traverse ienumerable<t> type objects, and the special thing about Parallel.ForEach is that it uses multithreading to execute code snippets within the loop body.
The most common forms of Parallel.ForEach are as follows:
public static Parallelloopresult ForEach<TSource> ( IEnumerablesource, Action<tsource > Body)
Plinq
PLINQ is also a programming model for parallel processing of data, which implements multithreaded parallel processing similar to Parallel.ForEach through LINQ syntax.
Scenario One: Parallel processing of independent operations of simple data (using Parallel.ForEach)
Example code:
Source, action<t> Action) { Parallel.ForEach (source, Element = = Action (element));
Reason:
1. Although PLINQ also provides a similar ForAll interface, it is too heavy for simple standalone operations.
2. Using Parallel.ForEach You can also set the Paralleloptions.maxdegreeofparalelism parameter (specifying the maximum number of threads required), so that when ThreadPool resources are scarce (even when the number of threads available < maxdegreeofparalelism), Parallel.ForEach can still run smoothly, and Parallel.ForEach can take advantage of these threads in a timely manner when more of the available threads appear. PLINQ can only require a fixed number of threads through the Withdegreeofparallelism method, namely: several are required, no more and no less.
Scenario Two: Parallel processing of sequential data (using PLINQ to maintain data order)
Using PLINQ's AsOrdered method is very simple and efficient when the output data sequence needs to remain in its original order.
Example code:
Grayscaletransformation (ienumerable<frame> Movie) {var processedmovie =movie.asparallel (). AsOrdered (). Select (frame = Converttograyscale (frame)); foreach (inprocessedmovie) {//Movie frames'll be evaluated lazily}}
Reason:
1. Parallel.ForEach implementation needs to go around some detours, first you need to use the following overloads in the method:
public static Parallelloopresult ForEach<tsource > ( IEnumerable<tsource> Source, Action<int64> body)
This overloaded Action contains the index parameter so that you can use this value to maintain the original sequence order when you output it. Take a look at the following example:
Double [] pairwisemultiply (double[] v2) { var length = math.min (v1. Length, V2. Lenth); return result;}
You may have realized that there is an obvious problem here: we used a fixed-length array. If the incoming is IEnumerable then you have 4 solutions:
(1) Call Ienumerable.count () to get the length of the data, then instantiate a fixed-length array with this value, and then use the code from the previous example.
(2) The second option would is to materialize the original collection before using it; The event that your input data set is prohibitively large, neither of the first and the options would be feasible. (not read the original)
(3) The third Way is to return a hash collection, which usually requires at least twice times the memory of the incoming data, so use caution when working with big data.
(4) Implement the sorting algorithm by itself (ensure that the incoming data and outgoing data are sorted in the same order)
2. By contrast, PLINQ's AsOrdered approach is so simple, and the method can handle streaming data, allowing incoming data to be deferred (lazy materialized)
Scenario Three: Parallel processing of streaming data (using PLINQ)
PLINQ can output streaming data, which is useful in a situation where:
1. The result set does not need to be a complete array of finished processing, i.e. only part of the information in the array is kept in memory at any point in time
2. You can traverse the output on a single thread (as if they already exist/have finished processing)
Example:
void Analyzestocks (ienumerable<stock> Stocks) {var stockriskportfolio =stocks.asparallel (). AsOrdered (). Select (new {stock = stock, Risk = Computerisk (stock)}). Where (stockrisk = expensiveriskanalysis (Stockrisk.risk)); foreach (instockriskportfolio) { Somestockcomputation (Stockrisk.risk); //Stockriskportfolio'll be a stream of results}}
Here, a single-threaded foreach is used to perform subsequent processing of PLINQ's output, and typically foreach does not have to wait for PLINQ to process all the data before it can start working.
PLINQ also allows you to specify how the output is cached, referring to the Withmergeoptions method of PLINQ, and the Parallelmergeoptions enumeration
Scenario four: Processing two collections (using PLINQ)
The Zip method of PLINQ provides a way to combine meta-calculation at the same time traversing two sets, and it can be combined with other query processing operations to achieve very complex functions.
Example:
Static ienumerable<t> zipping<t> (ienumerable<t> A, ienumerable<t> b) {Returna. AsParallel (). AsOrdered (). Select (element = expensivecomputation (Element)). Zip (B.asparallel (). AsOrdered (). Select (element = differentexpensivecomputation (Element)), (a_element, b_element) = Combine (a_element,b_ Element));}
The two data sources in the example can be processed in parallel, provided to ZIP for subsequent processing (Combine) when both parties have an available element.
Parallel.ForEach can also achieve similar ZIP processing:
Static ienumerable<t> zipping<t> (ienumerable<t> A, ienumerable<t> b) {var numElements = Math.min (A.count (), B.count ()); New t[numelements]; Parallel. ForEach (A, element, Loopstate, index) =>{var a_element = expensivecomputation (element); var b_element = differentexpensivecomputation (B.elementat (index)); Result[index] = Combine (a_element, b_element);}); return result;}
Of course, after using Parallel.ForEach you have to confirm that you want to maintain the original sequence, and be aware of the problem of array cross-border access.
Scenario Five: Thread local variables
Parallel.ForEach provides an overload of a thread-local variable, defined as follows:
public static Parallelloopresult ForEach<tlocal> ( IEnumerable<tsource> Source, func<tlocal> LocalInit, func<tlocal,tlocal> body, Action< tlocal> localfinally)
Examples of Use:
public static list<r> filtering<t,r> (ienumerable<t> source) {var Results = new list<r> (); using (Semaphoreslim SEM = new Semaphoreslim (1)) {Parallel. ForEach (source, () = new list<r> (), (element, Loopstate, localstorage) =>{bool filter = FilterFunction (element); return Localstorage;}, (Finalstorage) =>{lock (myLock) {results. AddRange (Finalstorage)};} return results;}
What are the advantages of thread-local variables? Take a look at the following example (a web crawler):
". Dat"); Console.WriteLine ("{0}:{1}", Thread.CurrentThread.ManagedThreadId, URL);});
Usually the first version of the code is written in this way, but the runtime will error "System.NotSupportedException-WebClient does not the support concurrent I/O operations." This is because multiple threads cannot access the same WebClient object at the same time. So we're going to define the WebClient object in the thread:
". Dat"); Console.WriteLine ("{0}:{1}", Thread.CurrentThread.ManagedThreadId, URL);});
There is still a problem after the modification because your machine is not a server, and a large number of instantiated WebClient quickly reach the maximum number of virtual connections allowed by your machine. Thread local variables can solve this problem:
public static void Downloadurlssafe () {Parallel.ForEach (URLs,New WebClient (),(URL, loopstate, index, WebClient) =>{webclient. DownloadFile (URL, filenames[index]+". Dat"); Console.WriteLine ("{0}:{1}", Thread.CurrentThread.ManagedThreadId, url); return webclient;},(webclient) = {});}
This way of writing guarantees that we can get enough WebClient instances, and that these WebClient instances are isolated from each other and only belong to their associated thread.
Although PLINQ provides a Threadlocal<t> object to achieve similar functionality:
void DownloadURL () {new threadlocal<webclient>(new WebClient ()); var res =urls. AsParallel (). ForAll (URL =>{webclient. Value.downloadfile (URL, Host[url] +". Dat")); Console.WriteLine ("{0}:{1}", Thread.CurrentThread.ManagedThreadId, URL);});
But be aware that:threadlocal<t> is more expensive!
Scenario Five: Exit operation (using Parallel.ForEach)
Parallel.ForEach has an overload declaration that contains a ParallelLoopState object:
public static Parallelloopresult ForEach<tsource > ( IEnumerable<tsource> Source, Action<parallelloopstate> body)
Parallelloopstate.stop () provides a way to exit the loop, which is faster than the other two methods. This method notifies the loop not to start executing new iterations, and to roll out the loop as quickly as possible.
The Parallelloopstate.isstopped property can be used to determine whether other iterations call the Stop method.
Example:
Static Boolean findany<t,t> (Ienumerable<t> tspace, T match) where t:iequalitycomparer<t>{ False Parallel. ForEach (Tspace, (Curvalue, loopstate) =>{true;loopstate. Stop ();}}); return matchfound;}
The parallelloopstate.break () notification loop continues the iteration before this element, but does not perform the iteration after this element. The first call to break works, and is recorded in the Parallelloopstate.lowestbreakiteration property. This processing is usually applied in an ordered lookup process, such as you have a sorted array, where you want to find the minimum index of the matching element, then you can use the following code:
static int findlowestindex<t,t> (ienumerable<t> tspace, T match) where t:iequalitycomparer<t>{ var loopresult = Parallel. ForEach (source, (Curvalue, Loopstate, Curindex) =>{if (curvalue.equals (match)) {loopstate. Break (); }); var matchedindex = loopresult.lowestbreakiteration; -1;}
Parallel.ForEach all the points of knowledge "turn"