Isso significa que dois threads não podem alterar os dados subjacentes simultaneamente? Ou significa que o segmento de código fornecido será executado com resultados previsíveis quando vários threads estiverem executando esse segmento de código?
multithreading
language-agnostic
programming-languages
concurrency
Varun Mahajan
fonte
fonte
Respostas:
Da Wikipedia:
consulte Mais informação:
http://en.wikipedia.org/wiki/Thread_safety
em alemão: http://de.wikipedia.org/wiki/Threadsicherheit
em francês: http://fr.wikipedia.org/wiki/Threadsafe (muito curto)
fonte
Código de thread-safe é um código que funcionará mesmo que muitos Threads o estejam executando simultaneamente.
http://mindprod.com/jgloss/threadsafe.html
fonte
Uma pergunta mais informativa é o que torna o código não seguro para threads - e a resposta é que existem quatro condições que devem ser verdadeiras ... Imagine o seguinte código (e é a tradução automática)
fonte
Gosto da definição da Concorrência Java na Prática de Brian Goetz por sua abrangência
"Uma classe é segura para threads se se comportar corretamente quando acessada de vários threads, independentemente do agendamento ou intercalação da execução desses threads pelo ambiente de tempo de execução, e sem sincronização adicional ou outra coordenação por parte do código de chamada. "
fonte
Como outros já apontaram, a segurança do encadeamento significa que um código funcionará sem erros se for usado por mais de um encadeamento de uma só vez.
Vale a pena estar ciente de que isso às vezes tem um custo, tempo de computador e codificação mais complexa, portanto nem sempre é desejável. Se uma classe puder ser usada com segurança em apenas um thread, talvez seja melhor fazê-lo.
Por exemplo, Java possui duas classes quase equivalentes
StringBuffer
eStringBuilder
. A diferença é queStringBuffer
é seguro para threads, portanto, uma única instância de aStringBuffer
pode ser usada por vários threads ao mesmo tempo.StringBuilder
não é seguro para threads e foi projetado como um substituto de alto desempenho para esses casos (a grande maioria) quando a String é criada por apenas um thread.fonte
O código de segurança de thread funciona como especificado, mesmo quando inserido simultaneamente por diferentes threads. Isso geralmente significa que estruturas de dados internas ou operações que devem ser executadas ininterruptamente são protegidas contra diferentes modificações ao mesmo tempo.
fonte
Uma maneira mais fácil de entender isso é o que torna o código não seguro para threads. Há dois problemas principais que farão com que um aplicativo encadeado tenha comportamento indesejado.
Acessando variável compartilhada sem bloqueio
Esta variável pode ser modificada por outro encadeamento durante a execução da função. Você deseja evitá-lo com um mecanismo de bloqueio para garantir o comportamento de sua função. A regra geral é manter a trava pelo menor tempo possível.
Impasse causado pela dependência mútua da variável compartilhada
Se você possui duas variáveis compartilhadas A e B. Em uma função, você bloqueia A primeiro e depois depois bloqueia B. Em outra função, você começa a bloquear B e, depois de um tempo, bloqueia A. é um impasse em potencial, onde a primeira função aguardará o desbloqueio de B quando a segunda função aguardará o desbloqueio de A. Esse problema provavelmente não ocorrerá no seu ambiente de desenvolvimento e apenas periodicamente. Para evitá-lo, todos os bloqueios devem sempre estar na mesma ordem.
fonte
Sim e não.
A segurança do encadeamento é um pouco mais do que apenas garantir que seus dados compartilhados sejam acessados apenas por um encadeamento por vez. Você deve garantir o acesso seqüencial aos dados compartilhados, evitando ao mesmo tempo condições de corrida , deadlocks , livelocks e falta de recursos .
Resultados imprevisíveis quando vários encadeamentos estão em execução não são uma condição necessária do código de segurança de encadeamentos, mas geralmente são um subproduto. Por exemplo, você pode ter um esquema produtor-consumidor configurado com uma fila compartilhada, um encadeamento de produtor e poucos encadeamentos de consumidor, e o fluxo de dados pode ser perfeitamente previsível. Se você começar a apresentar mais consumidores, verá mais resultados de aparência aleatória.
fonte
Em essência, muitas coisas podem dar errado em um ambiente multiencadeado (reordenação de instruções, objetos parcialmente construídos, mesma variável com valores diferentes em encadeamentos diferentes devido ao armazenamento em cache no nível da CPU etc.).
Gosto da definição dada pelo Java Concurrency in Practice :
Por corretamente, eles significam que o programa se comporta de acordo com suas especificações.
Exemplo artificial
Imagine que você implementa um contador. Você poderia dizer que se comporta corretamente se:
counter.next()
nunca retorna um valor que já foi retornado antes (assumimos que não há excesso, etc., por simplicidade)Um contador seguro de thread se comportaria de acordo com essas regras, independentemente de quantos threads o acessem simultaneamente (o que normalmente não seria o caso de uma implementação ingênua).
Nota: postagem cruzada em programadores
fonte
O Simply - code funcionará bem se muitos threads estiverem executando esse código ao mesmo tempo.
fonte
Não confunda segurança de linha com determinismo. Código de thread-safe também pode ser não determinístico. Dada a dificuldade de depurar problemas com código encadeado, esse é provavelmente o caso normal. :-)
A segurança do encadeamento simplesmente garante que, quando um encadeamento estiver modificando ou lendo dados compartilhados, nenhum outro encadeamento possa acessá-lo de maneira a alterar os dados. Se o seu código depender de uma determinada ordem de execução para correção, você precisará de outros mecanismos de sincronização além daqueles necessários para a segurança do encadeamento para garantir isso.
fonte
Gostaria de adicionar mais algumas informações sobre outras boas respostas.
A segurança do encadeamento implica que vários encadeamentos podem gravar / ler dados no mesmo objeto sem erros de inconsistência de memória. No programa altamente multiencadeado, um programa seguro para encadeamentos não causa efeitos colaterais nos dados compartilhados .
Dê uma olhada nesta pergunta SE para obter mais detalhes:
O que significa threadsafe?
O programa seguro de thread garante consistência de memória .
Na página de documentação da Oracle na API simultânea avançada:
Propriedades de consistência de memória:
O Capítulo 17 da Especificação de Linguagem Java ™ define a relação entre o que acontece antes nas operações de memória, como leituras e gravações de variáveis compartilhadas. Os resultados de uma gravação por um encadeamento são garantidos para serem visíveis para uma leitura por outro encadeamento apenas se a operação de gravação ocorrer antes da operação de leitura .
As construções
synchronized
evolatile
, assim como os métodosThread.start()
eThread.join()
, podem formar relacionamentos anteriores ao acontecimento.Os métodos de todas as classes
java.util.concurrent
e seus subpacotes estendem essas garantias à sincronização de nível superior. Em particular:Runnable
para umExecutor
acontecimento - antes de sua execução começar. Da mesma forma para Callables submetidos a umExecutorService
.Future
ação anterior ao processo subsequente à recuperação do resultado por meioFuture.get()
de outro encadeamento.Lock.unlock, Semaphore.release, and CountDownLatch.countDown
ações anteriores a um método de "aquisição" bem-sucedido, comoLock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.await
no mesmo objeto do sincronizador em outro encadeamento.Exchanger
, as ações anteriores aoexchange()
em cada encadeamento acontecem - antes daquelas subsequentes à troca correspondente () em outro encadeamento.CyclicBarrier.await
ePhaser.awaitAdvance
(bem como suas variantes) acontecem antes das ações executadas pela ação de barreira, e as ações executadas pela ação de barreira acontecem antes das ações subsequentes a um retorno bem-sucedido da espera correspondente em outros encadeamentos.fonte
Para completar outras respostas:
A sincronização é apenas uma preocupação quando o código no seu método faz uma de duas coisas:
Isso significa que as variáveis definidas DENTRO do seu método são sempre protegidas por thread. Toda chamada para um método tem sua própria versão dessas variáveis. Se o método for chamado por outro encadeamento, ou pelo mesmo encadeamento, ou mesmo se o método se chamar (recursão), os valores dessas variáveis não serão compartilhados.
O agendamento de encadeamentos não é garantido como rodízio . Uma tarefa pode monopolizar totalmente a CPU às custas de threads da mesma prioridade. Você pode usar Thread.yield () para ter consciência. Você pode usar (em java) Thread.setPriority (Thread.NORM_PRIORITY-1) para diminuir a prioridade de um thread
Além disso, cuidado com:
fonte
Sim e sim. Isso implica que os dados não são modificados por mais de um thread simultaneamente. No entanto, seu programa pode funcionar conforme o esperado e parecer seguro para threads, mesmo que não seja fundamentalmente.
Observe que a imprevisibilidade dos resultados é uma consequência das 'condições da corrida' que provavelmente resultam na modificação dos dados em uma ordem diferente da esperada.
fonte
Vamos responder isso por exemplo:
O
countTo10
método adiciona um ao contador e retorna true se a contagem tiver atingido 10. Ele deve retornar true apenas uma vez.Isso funcionará enquanto apenas um thread estiver executando o código. Se dois threads executam o código ao mesmo tempo, vários problemas podem ocorrer.
Por exemplo, se count começar como 9, um thread poderá adicionar 1 para contar (10), mas um segundo thread poderá inserir o método e adicionar 1 novamente (11) antes que o primeiro thread possa executar a comparação com 10 Então os dois threads fazem a comparação e descobrem que a contagem é 11 e nenhum retorna verdadeiro.
Portanto, esse código não é seguro para threads.
Em essência, todos os problemas de multiencadeamento são causados por alguma variação desse tipo de problema.
A solução é garantir que a adição e a comparação não possam ser separadas (por exemplo, envolvendo as duas instruções por algum tipo de código de sincronização) ou criando uma solução que não exija duas operações. Esse código seria seguro para threads.
fonte
Pelo menos em C ++, penso em thread-safe como um pouco impróprio, pois deixa muito a desejar. Para ser seguro para threads, o código geralmente precisa ser proativo . Geralmente não é uma qualidade passiva.
Para que uma classe seja segura, é necessário ter recursos "extras" que aumentam a sobrecarga. Esses recursos fazem parte da implementação da classe e, de um modo geral, ocultos da interface. Ou seja, threads diferentes podem acessar qualquer membro da classe sem ter que se preocupar em entrar em conflito com um acesso simultâneo por um thread diferente E podem fazê-lo de uma maneira muito preguiçosa, usando o estilo de codificação humano comum, sem precisar fazer isso. todas essas coisas loucas de sincronização que já estão reunidas nas entranhas do código que está sendo chamado.
E é por isso que algumas pessoas preferem usar o termo sincronizado internamente .
Conjuntos de terminologia
Existem três conjuntos principais de terminologias para essas idéias que encontrei. O primeiro e historicamente mais popular (mas pior) é:
O segundo (e melhor) é:
Um terço é:
Analogies
thread safe ~ prova de thread ~ sincronizada internamente
Um exemplo de sistema sincronizado internamente (também conhecido como thread-safe ou thread proof ) é um restaurante em que um host o recebe na porta e o impede de fazer fila. O anfitrião faz parte do mecanismo do restaurante para lidar com vários clientes e pode usar alguns truques bastante difíceis para otimizar o número de clientes em espera, como levar em consideração o tamanho de sua parte ou quanto tempo eles parecem ter. , ou até mesmo fazer reservas por telefone. O restaurante é sincronizado internamente porque tudo isso faz parte da interface para interagir com ele.
não é seguro para thread (mas agradável) ~ compatível com thread ~ sincronizado externamente ~ com thread livre
Suponha que você vá ao banco. Existe uma linha, ou seja, contenda para os caixas do banco. Por não ser selvagem, você reconhece que a melhor coisa a fazer no meio de uma disputa por um recurso é fazer fila como um ser civilizado. Ninguém tecnicamente faz você fazer isso. Esperamos que você tenha a programação social necessária para fazer isso por conta própria. Nesse sentido, o lobby do banco é sincronizado externamente. Devemos dizer que é inseguro? isso é o que a implicação é que se você ir com o thread-safe , thread-inseguro conjunto terminologia bipolar. Não é um conjunto muito bom de termos. A melhor terminologia é sincronizada externamente,O lobby do banco não é hostil ao acesso de vários clientes, mas também não faz o trabalho de sincronizá-los. Os clientes fazem isso eles mesmos.
Isso também é chamado de "encadeamento livre", onde "livre" é como "livre de piolhos" - ou, nesse caso, bloqueios. Bem, mais precisamente, primitivas de sincronização. Isso não significa que o código possa ser executado em vários threads sem essas primitivas. Significa apenas que ele não vem com eles já instalados e cabe a você, usuário do código, instalá-los como desejar. A instalação de suas próprias primitivas de sincronização pode ser difícil e requer muita reflexão sobre o código, mas também pode levar ao programa mais rápido possível, permitindo que você personalize como o programa é executado nas CPUs com hyperthread de hoje.
thread não seguro (e ruim) ~ thread hostil ~ não sincronizável
Um exemplo da analogia cotidiana de um sistema hostil ao segmento é um idiota com um carro esportivo que se recusa a usar os pisca-piscas e muda de faixa à vontade. O estilo de condução deles é hostil ou não sincronizável, porque você não tem como coordenar com eles, e isso pode levar à disputa pela mesma faixa, sem resolução e, portanto, um acidente quando dois carros tentam ocupar o mesmo espaço, sem nenhum protocolo para impeça isso. Esse padrão também pode ser pensado de maneira mais ampla como anti-social, o que eu prefiro porque é menos específico para threads e, portanto, mais geralmente aplicável a muitas áreas da programação.
Por que thread safe et al. é um conjunto de terminologia ruim
O primeiro e o mais antigo conjunto de terminologia falha ao fazer a distinção mais fina entre hostilidade do thread e compatibilidade de threads . A compatibilidade do encadeamento é mais passiva que a chamada segurança de encadeamento, mas isso não significa que o código chamado não seja seguro para o uso simultâneo de encadeamentos. Isso significa apenas que é passivo a sincronização que permitiria isso, transferindo-o para o código de chamada, em vez de fornecê-lo como parte de sua implementação interna. Compatível com thread é como o código provavelmente deve ser escrito por padrão na maioria dos casos, mas isso também é muitas vezes considerado erroneamente como thread inseguro, como se fosse inerentemente antissegurança, que é um ponto importante de confusão para os programadores.
NOTA: Muitos manuais de software realmente usam o termo "thread-safe" para se referir a "thread-compatible", adicionando ainda mais confusão ao que já era uma bagunça! Eu evito o termo "thread-safe" e "thread-inseguro" a todo custo por esse motivo, pois algumas fontes chamam algo de "thread-safe", enquanto outras o chamam de "thread-safe" porque não podem concordar se você precisa cumprir alguns padrões extras de segurança (primitivas de sincronização) ou NÃO ser hostil para ser considerado "seguro". Portanto, evite esses termos e use os termos mais inteligentes para evitar falhas de comunicação perigosas com outros engenheiros.
Lembrete de nossos objetivos
Essencialmente, nosso objetivo é subverter o caos.
Fazemos isso criando sistemas determinísticos nos quais podemos confiar. O determinismo é caro, principalmente devido aos custos de oportunidade de perda de paralelismo, pipelining e reordenação. Tentamos minimizar a quantidade de determinismo necessário para manter nossos custos baixos, além de evitar tomar decisões que corroerão ainda mais o pouco determinismo que podemos pagar.
A sincronização de threads é sobre aumentar a ordem e diminuir o caos. Os níveis nos quais você faz isso correspondem aos termos mencionados acima. O nível mais alto significa que um sistema se comporta de uma maneira totalmente previsível todas as vezes. O segundo nível significa que o sistema se comporta bem o suficiente para que o código de chamada possa detectar de forma confiável imprevisibilidade. Por exemplo, uma ativação espúria em uma variável de condição ou uma falha ao bloquear um mutex porque não está pronto. O terceiro nível significa que o sistema não se comporta bem o suficiente para jogar com mais ninguém e só pode ser executado SEMPRE com uma única thread sem incorrer em caos.
fonte
Em vez de pensar em código ou classes como thread-safe ou não, acho mais útil pensar em ações como sendo thread-safe. Duas ações são seguras para encadeamento se elas se comportarem conforme especificado quando executadas em contextos de encadeamento arbitrários. Em muitos casos, as classes suportam algumas combinações de ações de maneira segura para threads e outras não.
Por exemplo, muitas coleções, como listas de matrizes e conjuntos de hash, garantirão que, se forem acessadas inicialmente exclusivamente com um encadeamento e nunca forem modificadas depois que uma referência se tornar visível para outros encadeamentos, elas poderão ser lidas de maneira arbitrária por qualquer combinação de threads sem interferência.
O mais interessante é que algumas coleções de conjuntos de hash, como a original não genérica no .NET, podem oferecer uma garantia, desde que nenhum item seja removido e desde que apenas um thread as escreva, qualquer thread que tente ler a coleção se comportará como se estivesse acessando uma coleção em que as atualizações possam atrasar e ocorrer em ordem arbitrária, mas que, de outra forma, se comportarão normalmente. Se o segmento 1 adicionar X e, em seguida, Y, e o segmento 2 procurar e ver Y e, em seguida, X, seria possível que o segmento 2 visse que Y existe, mas X não; se esse comportamento é ou não "seguro para threads" dependerá de se o thread 2 está preparado para lidar com essa possibilidade.
Como observação final, algumas classes - especialmente o bloqueio de bibliotecas de comunicações - podem ter um método "close" ou "Dispose" que é seguro para threads em relação a todos os outros métodos, mas nenhum outro método que seja seguro para threads em relação a entre si. Se um encadeamento executar uma solicitação de leitura de bloqueio e um usuário do programa clicar em "cancelar", não haverá como uma solicitação de fechamento ser emitida pelo encadeamento que está tentando executar a leitura. A solicitação de fechamento / descarte, no entanto, pode definir de forma assíncrona um sinalizador que fará com que a solicitação de leitura seja cancelada o mais rápido possível. Depois que o fechamento é realizado em qualquer encadeamento, o objeto se torna inútil e todas as tentativas de ações futuras falham imediatamente,
fonte
Em palavras mais simples: P Se é seguro executar vários threads em um bloco de código, é seguro para threads *
*As condições se aplicam
As condições são mencionadas por outras respostas como 1. O resultado deve ser o mesmo se você executar um thread ou vários threads sobre ele etc.
fonte