Según mi entendimiento, dada una matriz C #, el acto de iterar sobre la matriz simultáneamente desde múltiples subprocesos es una operación segura para subprocesos.
Al iterar sobre la matriz, me refiero a leer todas las posiciones dentro de la matriz mediante un bucle antiguo simplefor
. Cada subproceso simplemente lee el contenido de una ubicación de memoria dentro de la matriz, nadie escribe nada, por lo que todos los subprocesos leen lo mismo de manera coherente.
Este es un fragmento de código que hace lo que escribí anteriormente:
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;
}
}
Entonces, entiendo que el método DoSomethingUseless
es seguro para subprocesos y que no hay necesidad de reemplazarlo string[]
con un tipo seguro para subprocesos (como, ImmutableArray<string>
por ejemplo).
Estoy en lo correcto ?
Ahora supongamos que tenemos una instancia de IEnumerable<T>
. No sabemos cuál es el objeto subyacente, solo sabemos que tenemos un objeto implementado IEnumerable<T>
, por lo que podemos iterar sobre él utilizando el foreach
bucle.
Según mi entendimiento, en este escenario no hay garantía de que iterar sobre este objeto desde varios subprocesos al mismo tiempo sea una operación segura para subprocesos. Dicho de otra manera, es completamente posible que iterar sobre la IEnumerable<T>
instancia desde diferentes hilos al mismo tiempo rompa el estado interno del objeto, de modo que se corrompa.
¿Estoy en lo cierto en este punto?
¿Qué pasa con la IEnumerable<T>
implementación de la Array
clase? ¿Es seguro para hilos?
Dicho de otra manera, ¿es seguro el siguiente hilo de código? (este es exactamente el mismo código que el anterior, pero ahora la matriz se repite utilizando un foreach
bucle en lugar de un for
bucle)
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;
}
}
¿Hay alguna referencia que indique qué IEnumerable<T>
implementaciones en la biblioteca de clase base .NET son realmente seguras para subprocesos?
fuente
Array
) será seguro para subprocesos siempre que todos los subprocesos solo lo lean. Lo mismo conList<T>
, y probablemente con cualquier otra colección escrita por Microsoft. Pero es posible hacer uno propioIEnumerable
que haga cosas que no sean seguras para subprocesos en el enumerador. Por lo tanto, no es una garantía de que todo lo que implementeIEnumerable
sea seguro para subprocesos.foreach
funciona con más que solo enumeradores. El compilador es lo suficientemente inteligente como para saber que si tiene información de tipo estático que indica que la colección es una matriz, omite generar el enumerador y solo accede a la matriz directamente como si fuera unfor
bucle. Del mismo modo, si la colección admite una implementación personalizada deGetEnumerator
, esa implementación se usa en lugar de llamarIEnumerable.GetEnumerator
. La noción de queforeach
solo se traficaIEnumerable/IEnumerator
es falsa.volatile
no garantiza que obtenga la actualización más reciente posible a una variable porque C # permite que diferentes hilos observen diferentes ordenamientos de lecturas y escrituras, y por lo tanto la noción de "más reciente" no está definida globalmente . En lugar de razonar sobre la frescura, razone sobre las restricciones quevolatile
impone la forma en que se pueden reordenar las escrituras.Respuestas:
¿La iteración sobre una matriz con un bucle for es una operación segura para subprocesos en C #?
Si está estrictamente hablando lectura desde varios subprocesos , que sea seguro para el hilo
Array
yList<T>
y casi todas las colecciones escrito por Microsoft, independientemente de si está utilizando unafor
oforeach
bucle. Especialmente en el ejemplo que tienes:Puede hacerlo a través de tantos hilos como desee. Todos leerán los mismos valores
Names
felizmente.Si le escribe desde otro hilo (esta no era su pregunta, pero vale la pena señalar)
Iterando sobre un
Array
oList<T>
con unfor
bucle, seguirá leyendo y felizmente leerá los valores modificados a medida que los encuentre.Iterando con un
foreach
bucle, entonces depende de la implementación. Si un valor en unaArray
parte cambia a través de unforeach
bucle, simplemente seguirá enumerando y le dará los valores cambiados.Con
List<T>
, depende de lo que consideres "seguro para subprocesos". Si está más interesado en leer datos precisos, entonces es "seguro", ya que arrojará una excepción a mitad de la enumeración y le dirá que la colección cambió. Pero si considera que lanzar una excepción no es seguro, entonces no es seguro.Pero vale la pena señalar que esta es una decisión de diseño
List<T>
, hay un código que busca cambios explícitamente y lanza una excepción. Las decisiones de diseño nos llevan al siguiente punto:¿Podemos suponer que cada colección que implementa
IEnumerable
es segura para leer en múltiples hilos?En la mayoría de los casos lo será, pero no se garantiza una lectura segura para subprocesos. La razón es porque todo
IEnumerable
requiere una implementación deIEnumerator
, que decide cómo atravesar los elementos de la colección. Y al igual que cualquier clase, puede hacer lo que quiera allí, incluidas cosas que no son seguras para subprocesos como:Incluso podría hacer algo extraño como
GetEnumerator()
devolver la misma instancia de su enumerador cada vez que se llama. Eso realmente podría generar algunos resultados impredecibles.Considero que algo no es seguro para subprocesos si puede dar lugar a resultados impredecibles. Cualquiera de esas cosas podría causar resultados impredecibles.
Puede ver el código fuente para el
Enumerator
queList<T>
usa , por lo que puede ver que no hace nada de esas cosas raras, lo que le dice que enumerarList<T>
desde múltiples hilos es seguro.fuente
List<T>
oArray
es tan seguro comoImmutableArray<string>
, ya que podemos probar que son seguros para leer en subprocesos. Solo buscaría otras opciones si planea leer y escribir en varios hilos. Podrías usar una colección apropiada deSystem.Collections.Concurrent
.Afirmar que su código es seguro para subprocesos significa que debemos dar por sentado sus palabras de que no hay ningún código dentro
UselessService
que intente reemplazar simultáneamente el contenido de laNames
matriz con algo como"tom" and "jerry"
o (más siniestro)null and null
. Por otra parte el uso de unImmutableArray<string>
le garantiza que el código es seguro para subprocesos, y todo el mundo podía estar seguro de eso con sólo mirar el tipo del campo de sólo lectura estática, sin tener que inspeccionar cuidadosamente el resto del código.Puede encontrar interesantes estos comentarios del código fuente de la
ImmutableArray<T>
, con respecto a algunos detalles de implementación de esta estructura:fuente