Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread safety of yield return with Parallel.ForEach()

Consider the following code sample, which creates an enumerable collection of integers and processes it in parallel:

using System.Collections.Generic;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {
        Parallel.ForEach(CreateItems(100), item => ProcessItem(item));
    }

    private static IEnumerable<int> CreateItems(int count)
    {
        for (int i = 0; i < count; i++)
        {
            yield return i;
        }
    }

    private static void ProcessItem(int item)
    {
        // Do something
    }
}

Is it guaranteed that the worker threads generated by Parallel.ForEach() each get a different item or is some locking mechanism around incrementation and returning of i required?

like image 745
Good Night Nerd Pride Avatar asked Jun 10 '13 10:06

Good Night Nerd Pride


People also ask

Is yield return thread safe?

As written it is thread safe but if you comment out the lock(_sync) in AllValues you should be able to verify that it is not thread safe by running it a few times. If you get an InvalidOperationException it proves that it is NOT thread safe.

Is ForEach thread safe?

The foreach operation itself is not thread safe. Say if you run a foreach loop to remove item from forward direction, it'll fail with "Collection was modified; enumeration operation may not execute." exception message.

Is parallel ForEach multithreaded?

The Parallel. ForEach method splits the work to be done into multiple tasks, one for each item in the collection. Parallel. ForEach is like the foreach loop in C#, except the foreach loop runs on a single thread and processing take place sequentially, while the Parallel.

Which is faster parallel ForEach or ForEach?

The execution of Parallel. Foreach is faster than normal ForEach.


2 Answers

Parallel.ForEach<TSource>, when TSource is an IEnumerable<T>, creates a partitioner for the IEnumerable<T> that includes its own internal locking mechanism, so you don't need to implement any thread-safety in your iterator.

Whenever a worker thread requests a chunk of items, the partitioner will create an internal enumerator, which:

  1. acquires a shared lock
  2. iterates through the source (from where it was left of) to retrieve the chunk of items, saving the items in an private array
  3. releases the lock so that other chunk requests can be fulfilled.
  4. serves the worker thread from its private array.

As you see, the run through the IEnumerable<T> for the purposes of partitioning is sequential (accessed via a shared lock), and the partitions are processed in parallel.

like image 182
Eren Ersönmez Avatar answered Oct 21 '22 08:10

Eren Ersönmez


TPL and PLINQ use the concept of partitioners.

Partitioner is a type, that inherits Partitioner<TSource> and serves for the splitting the source sequence into a number parts (or partitions). Built-in partitioners were designed to split the source sequence into nonoverlapping partitions.

like image 22
Dennis Avatar answered Oct 21 '22 09:10

Dennis