Com base no meu entendimento, dado um array C #, o ato de iterar sobre o array simultaneamente de vários threads é uma operação segura para threads.
Ao iterar sobre a matriz, quero dizer a leitura de todas as posições dentro da matriz por meio de um loop antigo simplesfor
. Cada thread está simplesmente lendo o conteúdo de um local de memória dentro da matriz, ninguém está escrevendo nada, então todos os threads lêem a mesma coisa de maneira consistente.
Este é um pedaço de código fazendo o que escrevi acima:
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;
}
}
Portanto, meu entendimento é de que o método DoSomethingUseless
é seguro para threads e que não há necessidade de substituí-lo string[]
por um tipo seguro de thread (como ImmutableArray<string>
por exemplo).
Estou correcto ?
Agora vamos supor que temos uma instância de IEnumerable<T>
. Não sabemos qual é o objeto subjacente, apenas sabemos que temos um objeto implementado IEnumerable<T>
, portanto, podemos iterá-lo usando o foreach
loop.
Com base no meu entendimento, nesse cenário, não há garantia de que a iteração desse objeto a partir de vários threads simultaneamente seja uma operação segura de thread. Em outras palavras, é perfeitamente possível que a iteração sobre a IEnumerable<T>
instância de diferentes threads ao mesmo tempo quebre o estado interno do objeto, para que ele seja corrompido.
Estou correto neste ponto?
E a IEnumerable<T>
implementação da Array
classe? É thread seguro?
Em outras palavras, o seguinte encadeamento de código é seguro? (esse é exatamente o mesmo código acima, mas agora a matriz é iterada usando um foreach
loop em vez de um 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;
}
}
Existe alguma referência informando que IEnumerable<T>
implementações na biblioteca de classes base do .NET são realmente seguras contra threads?
fonte
Array
) será segura para threads, desde que todas as threads estejam apenas lendo. O mesmo comList<T>
e provavelmente todas as outras coleções escritas pela Microsoft. Mas é possível criar o seu próprioIEnumerable
que faz coisas seguras sem thread no enumerador. Portanto, não é uma garantia de que tudo o que implementaIEnumerable
seja seguro para threads.foreach
funciona com mais do que apenas enumeradores. O compilador é inteligente o suficiente para saber que, se tiver informações de tipo estático indicando que a coleção é uma matriz, ele pula a geração do enumerador e acessa a matriz diretamente como se fosse umfor
loop. Da mesma forma, se a coleção suportar uma implementação personalizada deGetEnumerator
, essa implementação será usada em vez de chamarIEnumerable.GetEnumerator
. A noção em queforeach
apenas trafegaIEnumerable/IEnumerator
é falsa.volatile
não garante que você obtenha a atualização mais recente possível para uma variável porque o C # permite que diferentes segmentos observem diferentes ordenações de leituras e gravações e, portanto, a noção de "mais recente" não é definida globalmente . Em vez de raciocinar sobre atualização, raciocine sobre quais restriçõesvolatile
colocam a forma como as gravações podem ser reordenadas.Respostas:
A iteração sobre uma matriz com um loop for é uma operação segura de thread em C #?
Se você está falando estritamente sobre a leitura de vários segmentos , que será segmento seguro para
Array
eList<T>
e apenas sobre cada coleção escrito por Microsoft, independentemente de se você estiver usando umfor
ouforeach
loop. Especialmente no exemplo, você tem:Você pode fazer isso em quantos threads quiser. Todos lerão os mesmos valores
Names
felizes.Se você escrever a partir de outro tópico (essa não foi sua pergunta, mas vale a pena notar)
Iterando sobre um loop
Array
ouList<T>
com umfor
loop, ele continuará lendo e lerá alegremente os valores alterados à medida que você os encontrar.Iterando com um
foreach
loop, isso depende da implementação. Se um valor de uma formaArray
mudar parcialmente através de umforeach
loop, ele continuará enumerando e fornecendo os valores alterados.Com
List<T>
, depende do que você considera "thread safe". Se você está mais preocupado com a leitura de dados precisos, é meio "seguro", pois gera uma exceção no meio da enumeração e informa que a coleção foi alterada. Mas se você considera que lançar uma exceção não é segura, não é seguro.Mas vale a pena notar que esta é uma decisão de design
List<T>
, existe um código que procura explicitamente alterações e gera uma exceção. As decisões de design nos levam ao próximo ponto:Podemos assumir que toda coleção implementada
IEnumerable
é segura para ler em vários segmentos?Na maioria dos casos , será, mas a leitura segura de threads não é garantida. O motivo é que todos
IEnumerable
exigem uma implementação deIEnumerator
, que decide como percorrer os itens da coleção. E, como em qualquer classe, você pode fazer o que quiser , incluindo coisas não seguras para threads, como:Você pode até fazer algo estranho, como fazer
GetEnumerator()
retornar a mesma instância do seu enumerador toda vez que for chamado. Isso poderia realmente gerar alguns resultados imprevisíveis.Considero que algo não é seguro para threads, se puder resultar em resultados imprevisíveis. Qualquer uma dessas coisas pode causar resultados imprevisíveis.
Você pode ver o código-fonte do
Enumerator
queList<T>
usa , para que ele não faça nada estranho, o que indica que a enumeraçãoList<T>
de vários segmentos é segura.fonte
List<T>
ouArray
é tão seguro quantoImmutableArray<string>
, pois podemos provar que esses são tópicos seguros para ler. Eu só procuraria em outras opções se você planeja ler e escrever em vários threads. Você pode usar uma coleção apropriada deSystem.Collections.Concurrent
.Afirmar que seu código é seguro para threads significa que devemos tomar suas palavras como garantidas de que não há código dentro do
UselessService
que tentará substituir simultaneamente o conteúdo daNames
matriz por algo como"tom" and "jerry"
ou (mais sinistro)null and null
. Por outro lado, utilizando umImmutableArray<string>
iria garantir que o código é thread-safe, e todo mundo poderia ter certeza sobre isso só de olhar o tipo do campo somente leitura estático, sem ter de inspeccionar cuidadosamente o resto do código.Você pode achar interessantes esses comentários no código-fonte do
ImmutableArray<T>
, sobre alguns detalhes de implementação dessa estrutura:fonte