[英]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.如果您严格谈论从多个线程读取,那么对于
Array
和List<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
循环遍历Array
或List<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 一样,您可以在其中做任何您想做的事情,包括非线程安全的事情,例如:
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 ofthis
.如果成员需要引用数组字段,则视为取消引用
this
。 Calling other instance members (properties or methods) also counts as dereferencingthis
.调用其他实例成员(属性或方法)也算作取消引用
this
。 Any member that needs to usethis
more than once must instead assignthis
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.