O que significa threadsafe?

123

Recentemente, tentei acessar uma caixa de texto de um thread (que não seja o thread da interface do usuário) e uma exceção foi lançada. Ele dizia algo sobre o "código não ser seguro para threads" e, por isso, acabei escrevendo um delegado (exemplo do MSDN ajudou) e ligando para ele.

Mas mesmo assim eu não entendi direito por que todo o código extra era necessário.

Atualização: Terei algum problema sério se verificar

Controls.CheckForIllegalCrossThread..blah =true
Vivek Bernard
fonte
5
Normalmente, "thread safe" significa o que a pessoa que usa o termo pensa que significa, pelo menos para essa pessoa. Como tal, não é uma construção de linguagem muito útil - você precisa ser muito, muito mais específico ao falar sobre o comportamento do código encadeado.
@ Dave Desculpe, eu tentei procurar, mas desistiu ... obrigado de qualquer maneira ..
Vivek Bernard
1
um código que não surgeRace-Condition
Muhammad Babar

Respostas:

121

Eric Lippert tem um bom post intitulado O que você chama de "thread safe"? sobre a definição de segurança de threads, conforme encontrado na Wikipedia.

3 coisas importantes extraídas dos links:

“Um pedaço de código é seguro para threads se funcionar corretamente durante a execução simultânea de vários threads.”

"Em particular, ele deve satisfazer a necessidade de vários threads para acessar os mesmos dados compartilhados, ..."

"... e a necessidade de que um dado compartilhado seja acessado por apenas um segmento por vez."

Definitivamente vale a pena ler!

Gregory Pakosz
fonte
24
Evite respostas apenas do link, pois isso pode se tornar ruim a qualquer momento no futuro.
precisa saber é o seguinte
106

No mais simples dos termos, threadsafe significa que é seguro ser acessado a partir de vários threads. Quando você está usando vários threads em um programa e cada um deles tenta acessar uma estrutura ou local de dados comum na memória, várias coisas ruins podem acontecer. Então, você adiciona um código extra para evitar essas coisas ruins. Por exemplo, se duas pessoas estavam escrevendo o mesmo documento ao mesmo tempo, a segunda pessoa a salvar substituirá o trabalho da primeira pessoa. Para torná-lo seguro, é necessário forçar a pessoa 2 a aguardar que a pessoa 1 conclua sua tarefa antes de permitir que a pessoa 2 edite o documento.

Vincent Ramdhanie
fonte
11
Isso é chamado de sincronização. Certo?
JavaTechnical
3
Sim. Forçar os vários encadeamentos a aguardar o acesso a um recurso compartilhado pode ser realizado com a sincronização.
Vincent Ramdhanie
Pela resposta aceita por Gregory, ele está dizendo "" Um pedaço de código é seguro para threads se funcionar corretamente durante a execução simultânea de vários threads ". enquanto você está dizendo "Para tornar o segmento de seguros, em seguida, você tem que forçar pessoa 1 para esperar", não que ele está dizendo simultânea é aceitável enquanto você está dizendo que não é Pode me explicar?
Mel
É a mesma coisa. Estou apenas sugerindo um mecanismo simples como um exemplo do que torna o código seguro para threads. independentemente do mecanismo usado, embora vários threads executando o mesmo código não devam interferir entre si.
Vincent Ramdhanie 28/03
Então, isso só se aplica ao código que utiliza variáveis ​​globais e estáticas? Usando seu exemplo de pessoas editando documentos, suponho que não faça sentido impedir a pessoa 2 de executar o código de gravação de documento em outro documento.
precisa saber é o seguinte
18

A Wikipedia tem um artigo sobre segurança de threads.

Esta página de definições (você precisa pular um anúncio - desculpe) define assim:

Na programação de computadores, thread-safe descreve uma parte ou rotina do programa que pode ser chamada de vários threads de programação sem interação indesejada entre os threads.

Um encadeamento é um caminho de execução de um programa. Um único programa encadeado terá apenas um encadeamento e, portanto, esse problema não ocorre. Praticamente todos os programas da GUI têm vários caminhos de execução e, portanto, threads - existem pelo menos dois, um para processar a exibição da GUI e entregar a entrada do usuário e pelo menos um para executar as operações do programa.

Isso é feito para que a interface do usuário ainda seja responsiva enquanto o programa estiver trabalhando descarregando qualquer processo de execução longa para qualquer thread que não seja da interface do usuário. Esses encadeamentos podem ser criados uma vez e existirem durante toda a vida útil do programa, ou apenas criados quando necessários e destruídos quando terminados.

Como esses encadeamentos geralmente precisam executar ações comuns - E / S de disco, saída de resultados na tela etc. - essas partes do código precisam ser escritas de forma que possam lidar com a chamada de vários encadeamentos, geralmente em o mesmo tempo. Isso envolverá coisas como:

  • Trabalhando em cópias de dados
  • Adicionando bloqueios ao redor do código crítico
ChrisF
fonte
8

Simplesmente, thread safe significa que uma instância de método ou classe pode ser usada por vários threads ao mesmo tempo sem que ocorram problemas.

Considere o seguinte método:

private int myInt = 0;
public int AddOne()
{
    int tmp = myInt;
    tmp = tmp + 1;
    myInt = tmp;
    return tmp;
}

Agora, o segmento A e o segmento B gostariam de executar AddOne (). mas A começa primeiro e lê o valor de myInt (0) em tmp. Agora, por algum motivo, o planejador decide interromper o encadeamento A e adiar a execução para o encadeamento B. O encadeamento B agora também lê o valor de myInt (ainda 0) em sua própria variável tmp. O segmento B termina o método inteiro, portanto, no final, myInt = 1. E 1 é retornado. Agora é a vez do Tópico A novamente. O segmento A continua. E adiciona 1 ao tmp (tmp era 0 para o segmento A). E, em seguida, salva esse valor em myInt. myInt é novamente 1.

Portanto, nesse caso, o método AddOne foi chamado duas vezes, mas como o método não foi implementado de maneira segura para threads, o valor de myInt não é 2, conforme o esperado, mas 1 porque o segundo thread leu a variável myInt antes da conclusão do primeiro thread. atualizando-o.

Criar métodos seguros de encadeamento é muito difícil em casos não triviais. E existem algumas técnicas. Em Java, você pode marcar um método como sincronizado, isso significa que apenas um encadeamento pode executar esse método em um determinado momento. Os outros threads aguardam na fila. Isso torna um encadeamento de método seguro, mas se houver muito trabalho a ser feito em um método, isso desperdiçará muito espaço. Outra técnica é 'marcar apenas uma pequena parte de um método como sincronizado'criando uma trava ou semáforo e bloqueando essa pequena parte (geralmente chamada de seção crítica). Existem até alguns métodos implementados como thread-less safe, o que significa que eles são criados de tal maneira que vários threads podem percorrê-los ao mesmo tempo, sem nunca causar problemas; esse pode ser o caso quando apenas um método executa uma chamada atômica. Chamadas atômicas são chamadas que não podem ser interrompidas e só podem ser feitas por um encadeamento de cada vez.

Sujith PS
fonte
se o método AddOne foi chamado duas vezes
Sujith PS
6

No mundo real, o exemplo para o leigo é

Suponhamos que você tenha uma conta bancária na Internet e em serviços bancários móveis e sua conta tenha apenas US $ 10. Você efetuou o saldo da transferência para outra conta usando o mobile banking e, enquanto isso, fazia compras online usando a mesma conta bancária. Se essa conta bancária não for segura, o banco permitirá que você realize duas transações ao mesmo tempo e, em seguida, o banco irá à falência.

Threadsafe significa que o estado de um objeto não muda se simultaneamente vários threads tentarem acessar o objeto.

Yasir Shabbir Choudhary
fonte
5

Você pode obter mais explicações no livro "Concorrência Java na Prática":

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.

Jacky
fonte
4

Um módulo é seguro para threads se garantir que pode manter seus invariantes em face do uso multithread e simultâneo.

Aqui, um módulo pode ser uma estrutura de dados, classe, objeto, método / procedimento ou função. Parte do código com escopo básico e dados relacionados.

A garantia pode potencialmente ser limitada a determinados ambientes, como uma arquitetura específica da CPU, mas deve ser válida para esses ambientes. Se não houver delimitação explícita de ambientes, geralmente é considerado que é válido para todos os ambientes que o código possa ser compilado e executado.

Os módulos inseguros de thread podem funcionar corretamente sob uso simultâneo e com thread múltiplo, mas isso geralmente depende mais de sorte e coincidência do que de um design cuidadoso. Mesmo se algum módulo não quebrar para você, ele pode quebrar quando movido para outros ambientes.

Erros de multiencadeamento geralmente são difíceis de depurar. Alguns deles acontecem apenas ocasionalmente, enquanto outros se manifestam agressivamente - isso também pode ser específico do ambiente. Eles podem se manifestar como resultados sutilmente errados ou impasses. Eles podem atrapalhar as estruturas de dados de maneiras imprevisíveis e fazer com que outros erros aparentemente impossíveis apareçam em outras partes remotas do código. Pode ser muito específico da aplicação, por isso é difícil fornecer uma descrição geral.

Chris Vest
fonte
3

Segurança de thread : um programa seguro de thread protege seus dados contra erros de consistência de memória. Em um programa altamente multiencadeado, um programa seguro de encadeamento não causa efeitos colaterais com várias operações de leitura / gravação de vários encadeamentos nos mesmos objetos. Diferentes threads podem compartilhar e modificar dados do objeto sem erros de consistência.

Você pode obter segurança de encadeamento usando a API de simultaneidade avançada. Esta página de documentação fornece boas construções de programação para garantir a segurança do encadeamento.

Os objetos de bloqueio oferecem suporte a idiomas de bloqueio que simplificam muitos aplicativos simultâneos.

Os executivos definem uma API de alto nível para iniciar e gerenciar threads. As implementações de executores fornecidas pelo java.util.concurrent fornecem gerenciamento de conjunto de encadeamentos adequado para aplicativos de grande escala.

Coleções simultâneas facilitam o gerenciamento de grandes coleções de dados e podem reduzir bastante a necessidade de sincronização.

As variáveis ​​atômicas possuem recursos que minimizam a sincronização e ajudam a evitar erros de consistência de memória.

O ThreadLocalRandom (no JDK 7) fornece geração eficiente de números pseudo-aleatórios a partir de vários encadeamentos.

Consulte java.util.concurrent e java.util.concurrent.atomic pacotes também para outras construções de programação.

Ravindra babu
fonte
1

Você está trabalhando claramente em um ambiente WinForms. Os controles WinForms exibem afinidade de segmento, o que significa que o segmento no qual eles são criados é o único segmento que pode ser usado para acessá-los e atualizá-los. É por isso que você encontrará exemplos no MSDN e em outros lugares demonstrando como organizar a chamada de volta no thread principal.

A prática normal do WinForms é ter um único thread dedicado a todo o trabalho da interface do usuário.

David M
fonte
1

Eu acho que o conceito de http://en.wikipedia.org/wiki/Reentrancy_%28computing%29 é o que eu costumo considerar como threading inseguro, que é quando um método tem e depende de um efeito colateral, como uma variável global.

Por exemplo, eu vi código que formatou números de ponto flutuante como string, se dois deles são executados em threads diferentes, o valor global de decimalSeparator pode ser permanentemente alterado para '.'

//built in global set to locale specific value (here a comma)
decimalSeparator = ','

function FormatDot(value : real):
    //save the current decimal character
    temp = decimalSeparator

    //set the global value to be 
    decimalSeparator = '.'

    //format() uses decimalSeparator behind the scenes
    result = format(value)

    //Put the original value back
    decimalSeparator = temp
Aaron Robson
fonte
-2

Para entender a segurança das linhas, leia as seções abaixo :

4.3.1 Exemplo: Rastreador de veículos usando delegação

Como um exemplo mais substancial de delegação, vamos construir uma versão do rastreador de veículo que delega para uma classe de thread-safe. Armazenamos os locais em um mapa e, portanto, começamos com uma implementação de mapa com segurança para threads ConcurrentHashMap. Também armazenamos o local usando uma classe Point imutável em vez de MutablePoint, mostrada na Listagem 4.6.

Listagem 4.6. Classe de ponto imutável usada pelo DelegatingVehicleTracker.

 class Point{
  public final int x, y;

  public Point() {
        this.x=0; this.y=0;
    }

  public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

}

Pointé seguro para threads porque é imutável. Os valores imutáveis ​​podem ser compartilhados e publicados livremente, portanto, não precisamos mais copiar os locais ao devolvê-los.

DelegatingVehicleTrackerna Listagem 4.7 não usa nenhuma sincronização explícita; todo o acesso ao estado é gerenciado ConcurrentHashMape todas as chaves e valores do mapa são imutáveis.

Listagem 4.7. Delegando a segurança do encadeamento a um ConcurrentHashMap.

  public class DelegatingVehicleTracker {

  private final ConcurrentMap<String, Point> locations;
    private final Map<String, Point> unmodifiableMap;

  public DelegatingVehicleTracker(Map<String, Point> points) {
        this.locations = new ConcurrentHashMap<String, Point>(points);
        this.unmodifiableMap = Collections.unmodifiableMap(locations);
    }

  public Map<String, Point> getLocations(){
        return this.unmodifiableMap; // User cannot update point(x,y) as Point is immutable
    }

  public Point getLocation(String id) {
        return locations.get(id);
    }

  public void setLocation(String id, int x, int y) {
        if(locations.replace(id, new Point(x, y)) == null) {
             throw new IllegalArgumentException("invalid vehicle name: " + id);
        }
    }

}

Se tivéssemos usado a MutablePointclasse original em vez de Point, estaríamos quebrando o encapsulamento, deixando getLocationspublicar uma referência ao estado mutável que não é seguro para threads. Observe que alteramos um pouco o comportamento da classe rastreador de veículos; enquanto a versão do monitor retornou uma captura instantânea dos locais, a versão delegada retorna uma visualização inalterável, mas "ao vivo", dos locais dos veículos. Isso significa que se o segmento A chama getLocationse o segmento B modifica posteriormente o local de alguns dos pontos, essas alterações são refletidas no mapa retornado ao segmento A.

4.3.2 Variáveis ​​de estado independentes

Também podemos delegar a segurança do encadeamento para mais de uma variável de estado subjacente, desde que as variáveis ​​de estado subjacentes sejam independentes, o que significa que a classe composta não impõe nenhum invariável envolvendo as variáveis ​​de estado múltiplas.

VisualComponentna Listagem 4.9 é um componente gráfico que permite aos clientes registrar ouvintes para eventos de mouse e pressionamento de tecla. Ele mantém uma lista de ouvintes registrados de cada tipo, para que, quando um evento ocorrer, os ouvintes apropriados possam ser chamados. Mas não há relação entre o conjunto de ouvintes de mouse e ouvintes principais; os dois são independentes e, portanto, VisualComponentpodem delegar suas obrigações de segurança de encadeamento em duas listas subjacentes de segurança de encadeamento.

Listagem 4.9. Delegando a segurança do encadeamento a várias variáveis ​​de estado subjacente.

public class VisualComponent {
    private final List<KeyListener> keyListeners 
                                        = new CopyOnWriteArrayList<KeyListener>();
    private final List<MouseListener> mouseListeners 
                                        = new CopyOnWriteArrayList<MouseListener>();

  public void addKeyListener(KeyListener listener) {
        keyListeners.add(listener);
    }

  public void addMouseListener(MouseListener listener) {
        mouseListeners.add(listener);
    }

  public void removeKeyListener(KeyListener listener) {
        keyListeners.remove(listener);
    }

  public void removeMouseListener(MouseListener listener) {
        mouseListeners.remove(listener);
    }

}

VisualComponentusa a CopyOnWriteArrayListpara armazenar cada lista de ouvintes; Esta é uma implementação de lista segura para threads, particularmente adequada para gerenciar listas de ouvintes (consulte a Seção 5.2.3). Cada lista é segura para threads e, como não há restrições que acoplem o estado de um ao estado do outro, VisualComponentpode delegar suas responsabilidades de segurança de thread no subjacente mouseListenerse nos keyListenersobjetos.

4.3.3 Quando a delegação falha

A maioria das classes compostas não é tão simples quanto VisualComponent: elas possuem invariantes que relacionam suas variáveis ​​de estado componentes. NumberRangena Listagem 4.10 usa dois AtomicIntegerspara gerenciar seu estado, mas impõe uma restrição adicional - que o primeiro número seja menor ou igual ao segundo.

Listagem 4.10. Classe de intervalo de números que não protege suficientemente seus invariantes. Não faça isso.

public class NumberRange {

  // INVARIANT: lower <= upper
    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);

  public void setLower(int i) {
        //Warning - unsafe check-then-act
        if(i > upper.get()) {
            throw new IllegalArgumentException(
                    "Can't set lower to " + i + " > upper ");
        }
        lower.set(i);
    }

  public void setUpper(int i) {
        //Warning - unsafe check-then-act
        if(i < lower.get()) {
            throw new IllegalArgumentException(
                    "Can't set upper to " + i + " < lower ");
        }
        upper.set(i);
    }

  public boolean isInRange(int i){
        return (i >= lower.get() && i <= upper.get());
    }

}

NumberRangenão é seguro para threads ; não preserva o invariante que restringe o inferior e o superior. Os métodos setLowere setUppertentam respeitar esse invariável, mas o fazem mal. Ambas setLowere setUppersão sequências de verificação e ação, mas não usam bloqueio suficiente para torná-las atômicas. Se o intervalo de números for mantido (0, 10) e um segmento ligar setLower(5)enquanto outro segmento setUpper(4), com algum tempo azarado, ambos passarão nas verificações nos setters e as duas modificações serão aplicadas. O resultado é que o intervalo agora mantém (5, 4) - um estado inválido . Portanto, enquanto os AtomicIntegers subjacentes são seguros para threads, a classe composta não é . Porque as variáveis ​​de estado subjacentes lowereuppernão são independentes, NumberRangenão podem simplesmente delegar a segurança do encadeamento às suas variáveis ​​de estado seguras.

NumberRangepode ser tornado seguro para threads usando o bloqueio para manter seus invariantes, como proteger as partes inferior e superior com uma trava comum. Ele também deve evitar a publicação inferior e superior para impedir que os clientes subvertam seus invariantes.

Se uma classe tiver ações compostas NumberRange, a delegação sozinha não é novamente uma abordagem adequada para a segurança do encadeamento. Nesses casos, a classe deve fornecer seu próprio bloqueio para garantir que as ações compostas sejam atômicas, a menos que toda a ação composta também possa ser delegada às variáveis ​​de estado subjacentes.

Se uma classe for composta de várias variáveis ​​de estado seguras de encadeamento independentes e não tiver operações que possuam transições de estado inválidas, poderá delegar segurança de encadeamento às variáveis ​​de estado subjacentes.

excesso de troca
fonte