简体   繁体   English

使用 for 循环遍历数组是否是 C# 中的线程安全操作? 迭代一个 IEnumerable 怎么样<t>有一个 foreach 循环?</t>

[英]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.根据我的理解,给定一个 C# 数组,从多个线程同时迭代数组的行为线程安全操作。

By iterating over the array I mean reading all the positions inside the array by means of a plain old for loop .通过遍历数组,我的意思是通过普通的for循环读取数组内的所有位置。 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.每个线程只是读取数组内 memory 位置的内容,没有人在写任何东西,所以所有线程都以一致的方式读取相同的东西。

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).所以,我的理解是DoSomethingUseless方法是线程安全的,不需要string[]替换为线程安全类型(例如ImmutableArray<string> )。

Am I correct?我对么?

Now let's suppose that we have an instance of IEnumerable<T> .现在让我们假设我们有一个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.我们不知道底层的 object 是什么,我们只知道我们有一个实现IEnumerable<T>的 object ,因此我们可以使用foreach循环对其进行迭代。

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.根据我的理解,在这种情况下,不能保证同时从多个线程迭代此 object 是线程安全操作。 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.换句话说,完全有可能同时从不同线程迭代IEnumerable<T>实例会破坏 object 的内部 state,从而使其损坏。

Am I correct on this point?我在这一点上正确吗?

What about the IEnumerable<T> implementation of the Array class? Array class 的IEnumerable<T>实现怎么样? 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) (这与上面的代码完全相同,但现在使用foreach循环而不是for循环来迭代数组)

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?是否有任何参考说明 .NET 基础 class 库中的哪些IEnumerable<T>实现实际上是线程安全的?

Is iterating over an array with a for loop a thread safe operation in C#?使用 for 循环遍历数组是否是 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.如果您严格谈论从多个线程读取,那么对于ArrayList<T>以及 Microsoft 编写的几乎每个集合都是线程安全的,无论您使用的是for还是foreach循环。 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.他们都会愉快地从Names中读取相同的值。

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.使用for循环遍历ArrayList<T> ,它只会继续读取,并且会在您遇到更改的值时愉快地读取它们。

Iterating with a foreach loop, then it depends on the implementation.使用foreach循环进行迭代,然后取决于实现。 If a value in an Array changes part way through a foreach loop, it will just keep enumerating and give you the changed values.如果Array中的值在foreach循环中发生了部分变化,它只会继续枚举并为您提供更改的值。

With List<T> , it depends what you consider "thread safe".使用List<T> ,这取决于您认为“线程安全”的内容。 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.但值得注意的是,这是List<T>中的设计决策, 有代码明确查找更改并引发异常。 Design decisions brings us to the next point:设计决策将我们带到了下一点:

Can we assume that every collection that implements IEnumerable is safe to read across multiple threads?我们可以假设每个实现IEnumerable的集合都可以安全地跨多个线程读取吗?

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.原因是因为每个IEnumerable都需要IEnumerator的实现,它决定如何遍历集合中的项目。 And just like any class, you can do anything you want in there, including non-thread-safe things like:就像任何 class 一样,您可以在其中做任何您想做的事情,包括非线程安全的事情,例如:

  • Using static variables使用 static 变量
  • 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.你甚至可以做一些奇怪的事情,比如让GetEnumerator()每次调用它时都返回相同的枚举器实例。 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.您可以看到List<T>使用的Enumerator的源代码,因此您可以看到它没有做任何奇怪的事情,这告诉您从多个线程枚举List<T>是安全的。

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 .断言您的代码是线程安全的意味着我们必须理所当然地认为UselessService中没有代码会尝试同时用"tom" and "jerry"类的东西替换Names数组的内容或(更险恶) 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.另一方面,使用ImmutableArray<string>可以保证代码是线程安全的,每个人都可以通过查看 static 只读字段的类型来确保这一点,而无需仔细检查代码的 rest。

You may find interesting these comments from the source code of the ImmutableArray<T> , regarding some implementation details of this struct:您可能会从ImmutableArray<T>源代码中发现有趣的这些注释,这些注释涉及此结构的一些实现细节:

A readonly array with O(1) indexable lookup time.具有 O(1) 可索引查找时间的只读数组。

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.我们自己的System.Collections.Immutable.ImmutableInterlocked class 依赖于它,以及其他外部。

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.因此,每个成员都应该只取消引用this ONCE,这一点非常重要。 If a member needs to reference the array field, that counts as a dereference of this .如果成员需要引用数组字段,则视为取消引用this Calling other instance members (properties or methods) also counts as dereferencing this .调用其他实例成员(属性或方法)也算作取消引用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的成员都必须将this分配给局部变量,并将其用于代码的 rest。 This effectively copies the one field in the struct to a local variable so that it is insulated from other threads.这有效地将结构中的一个字段复制到局部变量,以便与其他线程隔离。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM