Pergunta

Eu tenho uma classe singleton C# que várias classes usam.O acesso é através Instance para o Toggle() método thread-safe?Se sim, por quais suposições, regras, etc.Se não, por que e como posso consertar isso?

public class MyClass
{
    private static readonly MyClass instance = new MyClass();

    public static MyClass Instance
    {
        get { return instance; }
    }

    private int value = 0;

    public int Toggle()
    {
        if(value == 0) 
        {
            value = 1; 
        }
        else if(value == 1) 
        { 
            value = 0; 
        }

        return value;
    }
}
Foi útil?

Solução

O acesso através de 'Instância' à classe 'Toggle ()' é seguro para threads?Se sim, por quais suposições, regras, etc.Se não, por que e como posso consertar isso?

Não, não é seguro para threads.

Basicamente, ambos os threads podem executar o Toggle funcionar ao mesmo tempo, então isso pode acontecer

    // thread 1 is running this code
    if(value == 0) 
    {
        value = 1; 
        // RIGHT NOW, thread 2 steps in.
        // It sees value as 1, so runs the other branch, and changes it to 0
        // This causes your method to return 0 even though you actually want 1
    }
    else if(value == 1) 
    { 
        value = 0; 
    }
    return value;

Você precisa operar com a seguinte suposição.

Se 2 threads estiverem em execução, eles podem e irão intercalar e interagir entre si aleatoriamente a qualquer momento.Você pode estar na metade da escrita ou leitura de um número inteiro ou flutuante de 64 bits (em uma CPU de 32 bits) e outro thread pode entrar e alterá-lo abaixo de você.

Se os dois threads nunca acessam nada em comum, não importa, mas assim que o fizerem, você precisa evitar que eles pisem um no outro.A maneira de fazer isso no .NET é com bloqueios.

Você pode decidir o que e onde bloquear pensando em coisas como estas:

Para um determinado bloco de código, se o valor de something foi trocado debaixo de mim, isso importaria?Se sim, você precisa bloquear isso something durante a duração do código onde for importante.

Olhando para o seu exemplo novamente

    // we read value here
    if(value == 0) 
    {
        value = 1; 
    }
    else if(value == 1) 
    { 
        value = 0; 
    }
    // and we return it here
    return value;

Para que isso retorne o que esperamos, assumimos que value não será alterado entre a leitura e a return.Para que esta suposição esteja realmente correta, você precisa bloquear value durante esse bloco de código.

Então você faria isso:

lock( value )
{
     if(value == 0) 
     ... // all your code here
     return value;
}

NO ENTANTO

No .NET você só pode bloquear tipos de referência.Int32 é um tipo de valor, portanto não podemos bloqueá-lo.
Resolvemos isso introduzindo um objeto 'fictício' e bloqueando que onde quer que queiramos bloquear 'valor'.

Isso é o que Ben Scheirman está se referindo.

Outras dicas

A implementação original não é segura para threads, como Ben aponta

Uma maneira simples de torná-lo thread-safe é introduzir uma instrução lock.Por exemplo.assim:

public class MyClass
{
    private Object thisLock = new Object();
    private static readonly MyClass instance = new MyClass();
    public static MyClass Instance
    {
        get { return instance; }
    }
    private Int32 value = 0;
    public Int32 Toggle()
    {
        lock(thisLock)
        {
            if(value == 0) 
            {
                value = 1; 
            }
            else if(value == 1) 
            { 
                value = 0; 
            }
            return value;
        }
    }
}

Isso é o que eu pensei.Mas, estou procurando os detalhes ...'Toggle ()' não é um método estático, mas é um membro de uma propriedade estática (ao usar 'instância').É isso que o torna compartilhado entre os threads?

Se o seu aplicativo for multithread e você puder prever que vários threads acessarão esse método, isso o tornará compartilhado entre os threads.Como sua classe é um Singleton, você sabe que diferentes threads acessarão o mesmo objeto, portanto, tome cuidado com a segurança de thread de seus métodos.

E como isso se aplica a singletons em geral.Eu teria que abordar isso em todos os métodos da minha classe?

Como eu disse acima, por ser um singleton você sabe que diferentes threads acessarão o mesmo objeto, possivelmente ao mesmo tempo.Isso não significa que você precise fazer com que todos os métodos obtenham um bloqueio.Se você perceber que uma invocação simultânea pode levar ao estado corrompido da classe, você deve aplicar o método mencionado por @Thomas

Posso presumir que o padrão singleton expõe minha adorável classe thread-safe a todos os problemas de thread de membros estáticos regulares?

Não.Sua classe simplesmente não é segura para threads.O singleton não tem nada a ver com isso.

(Estou entendendo o fato de que membros da instância chamados em um objeto estático causam problemas de threading)

Também não tem nada a ver com isso.

Você tem que pensar assim:É possível no meu programa que 2 (ou mais) threads acessem esse dado ao mesmo tempo?

O fato de você obter os dados por meio de um singleton, ou variável estática, ou passar um objeto como parâmetro de método não importa.No final das contas, são apenas alguns bits e bytes na RAM do seu PC, e tudo o que importa é se vários threads podem ver os mesmos bits.

Seu thread pode parar no meio desse método e transferir o controle para um thread diferente.Você precisa de uma seção crítica em torno desse código ...

private static object _lockDummy = new object();


...

lock(_lockDummy)
{
   //do stuff
}

Eu também adicionaria um construtor protegido ao MyClass para evitar que o compilador gerasse um construtor público padrão.

Eu estava pensando que se eu descartasse o padrão singleton e forçasse todos a obter uma nova instância da classe, isso aliviaria alguns problemas...mas isso não impede ninguém de inicializar um objeto estático desse tipo e passá-lo por aí ...ou de desmembrar vários threads, todos acessando 'Toggle()' da mesma instância.

Bingo :-)

Agora eu entendi.É um mundo difícil.Eu gostaria de não estar refatorando o código legado :(

Infelizmente, o Multithreading é difícil e você precisa ser muito paranóico sobre as coisas :-) A solução mais simples neste caso é manter o singleton e adicionar uma trava ao redor do valor, como nos exemplos.

Bem, na verdade eu não conheço muito bem C#...mas estou bem em Java, então darei a resposta para isso e espero que os dois sejam semelhantes o suficiente para serem úteis.Se não, peço desculpas.

A resposta é: não, não é seguro.Um thread poderia chamar Toggle() ao mesmo tempo que o outro, e é possível, embora improvável com este código, que Thread1 possa definir value entre os momentos em que Thread2 verifica e quando o define.

Para corrigir, basta fazer Toggle() synchronized.Ele não bloqueia nada nem chama nada que possa gerar outro thread que possa chamar Toggle(), então isso é tudo que você precisa fazer para salvá-lo.

Citar:

if(value == 0) { value = 1; }
if(value == 1) { value = 0; }
return value;

value sempre será 0...

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top