Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is iterating over an array with a for loop a thread safe operation in C# ? What about iterating an IEnumerable<T> with a foreach loop?

Based on my understanding, given a C# array, the act of iterating over the array concurrently from multiple threads is a thread safe operation.

By iterating over the array I mean reading all the positions inside the array by means of a plain old for loop. Each thread is simply reading the content of a memory location inside the array, no one is writing anything so all the threads read the same thing in a consistent manner.

This is a piece of code doing what I wrote above:

public class UselessService 
{
   private static readonly string[] Names = new [] { "bob", "alice" };

   public List<int> DoSomethingUseless()
   {
      var temp = new List<int>();

      for (int i = 0; i < Names.Length; i++) 
      {
        temp.Add(Names[i].Length * 2);
      }

      return temp;
   }
}

So, my understanding is that the method DoSomethingUseless is thread safe and that there is no need to replace the string[] with a thread safe type (like ImmutableArray<string> for instance).

Am I correct ?

Now let's suppose that we have an instance of IEnumerable<T>. We don't know what the underlying object is, we just know that we have an object implementing IEnumerable<T>, so we are able to iterate over it by using the foreach loop.

Based on my understanding, in this scenario there is no guarantee that iterating over this object from multiple threads concurrently is a thread safe operation. Put another way, it is entirely possible that iterating over the IEnumerable<T> instance from different threads at the same time breaks the internal state of the object, so that it becomes corrupted.

Am I correct on this point ?

What about the IEnumerable<T> implementation of the Array class ? Is it thread safe ?

Put another way, is the following code thread safe ? (this is exactly the same code as above, but now the array is iterated by using a foreach loop instead of a for loop)

public class UselessService 
{
   private static readonly string[] Names = new [] { "bob", "alice" };

   public List<int> DoSomethingUseless()
   {
      var temp = new List<int>();

      foreach (var name in Names) 
      {
        temp.Add(name.Length * 2);
      }

      return temp;
   }
}

Is there any reference stating which IEnumerable<T> implementations in the .NET base class library are actually thread safe ?

like image 656
Enrico Massone Avatar asked Nov 14 '19 21:11

Enrico Massone


2 Answers

Is iterating over an array with a for loop a thread safe operation in C# ?

If you're strictly talking about reading from multiple threads, that will be thread safe for Array and List<T> and just about every collection written by Microsoft, regardless of if you're using a for or foreach loop. Especially in the example you have:

var temp = new List<int>();

foreach (var name in Names)
{
  temp.Add(name.Length * 2);
}

You can do that across as many threads as you want. They'll all read the same values from Names happily.

If you write to it from another thread (this wasn't your question, but it's worth noting)

Iterating over an Array or List<T> with a for loop, it'll just keep reading, and it'll happily read the changed values as you come across them.

Iterating with a foreach loop, then it depends on the implementation. If a value in an Array changes part way through a foreach loop, it will just keep enumerating and give you the changed values.

With List<T>, it depends what you consider "thread safe". If you are more concerned with reading accurate data, then it kind of is "safe" since it will throw an exception mid-enumeration and tell you that the collection changed. But if you consider throwing an exception to be not safe, then it's not safe.

But it's worth noting that this is a design decision in List<T>, there is code that explicitly looks for changes and throws an exception. Design decisions brings us to the next point:

Can we assume that every collection that implements IEnumerable is safe to read across multiple threads?

In most cases it will be, but thread-safe reading is not guaranteed. The reason is because every IEnumerable requires an implementation of IEnumerator, which decides how to traverse the items in the collection. And just like any class, you can do anything you want in there, including non-thread-safe things like:

  • Using static variables
  • Using a shared cache for reading values
  • Not making any effort to handle cases where the collection changes mid-enumeration
  • etc.

You could even do something weird like make GetEnumerator() return the same instance of your enumerator every time its called. That could really make for some unpredictable results.

I consider something to not be thread safe if it can result in unpredictable results. Any of those things could cause unpredictable results.

You can see the source code for the Enumerator that List<T> uses, so you can see that it doesn't do any of that weird stuff, which tells you that enumerating List<T> from multiple threads is safe.

like image 168
Gabriel Luci Avatar answered Oct 06 '22 16:10

Gabriel Luci


To assert that your code is thread-safe means that we must take your words for granted that there is no code inside the UselessService that will try to replace concurrently the contents of the Names array with something like "tom" and "jerry" or (more sinister) null and null. On the other hand using an ImmutableArray<string> would guarantee that the code is thread-safe, and everybody could be assured about that just by looking the type of the static readonly field, without having to inspect carefully the rest of the code.

You may find interesting these comments from the source code of the ImmutableArray<T>, regarding some implementation details of this struct:

A readonly array with O(1) indexable lookup time.

This type has a documented contract of being exactly one reference-type field in size. Our own System.Collections.Immutable.ImmutableInterlocked class depends on it, as well as others externally.

IMPORTANT NOTICE FOR MAINTAINERS AND REVIEWERS:

This type should be thread-safe. As a struct, it cannot protect its own fields from being changed from one thread while its members are executing on other threads because structs can change in place simply by reassigning the field containing this struct. Therefore it is extremely important that Every member should only dereference this ONCE. If a member needs to reference the array field, that counts as a dereference of this. Calling other instance members (properties or methods) also counts as dereferencing this. Any member that needs to use this more than once must instead assign this to a local variable and use that for the rest of the code instead. This effectively copies the one field in the struct to a local variable so that it is insulated from other threads.

like image 1
Theodor Zoulias Avatar answered Oct 06 '22 14:10

Theodor Zoulias