Pergunta

Então, enquanto isso, sabemos que o travamento verificado como é não funciona no C ++, pelo menos não de maneira portátil.

Acabei de perceber que tenho uma implementação frágil em uma praça preguiçosa que uso para um traçador de raios de terreno. Por isso, tentei encontrar uma maneira de ainda usar a inicialização preguiçosa de maneira segura, pois não gostaria de quadruplicar o uso da memória e reordenar grandes partes de algoritmos implementados.

Esta travessia é inspirada no padrão na página 12 de C ++ e os perigos do bloqueio verificado duas vezes, mas tenta fazer isso mais barato:

(pseudo code!)

struct Foo {
    bool childCreated[4];
    Mutex mutex[4];
    Foo child[4];

    void traverse (...) {
        ...
        if (!childCreated[c]) { 
            // get updated view
            #pragma flush childCreated[c]
            if (!childCreated[c]) { 
                ScopedLock sl (mutex[c]);
                if (!childCreated[c]) {
                    create (c);
                    #pragma flush childCreated[c]  
                    childCreated[c] = true;
                }
            }
        }
    }
}

É assumido que #pragma flush também serviria como um ponto de sequência rígido em que compiladores e processadores não poderão reordenar as operações entre eles.

Quais problemas você vê?

editar: Versão 2, tentando levar em consideração os Vlads Answer (Introduce Terceiro Flush):

(pseudo code!)

struct Foo {
    bool childCreated[4];
    Mutex mutex[4];
    Foo child[4];

    void traverse (...) {
        ...
        if (!childCreated[c]) { 
            // get updated view
            #pragma flush childCreated[c]
            if (!childCreated[c]) { 
                ScopedLock sl (mutex[c]);
                #pragma flush childCreated[c]
                if (!childCreated[c]) {
                    create (c);
                    #pragma flush childCreated[c]
                    childCreated[c] = true;
                }
            }
        }
    }
}

editar: Versão 3, de alguma forma acho isso bastante equivalente à versão 2, porque não estou usando a própria criança, mas uma bandeira primitiva para verificar a validade, basicamente confiando em uma barreira de memória entre criar uma criança e escrever para esse sinalizador.

(pseudo code!)

struct Foo {
    bool childCreated[4];
    Mutex mutex[4];
    Foo child[4];

    void traverse (...) {
        ...
        if (!childCreated[c]) { 
            ScopedLock sl (mutex[c]);
            #pragma flush childCreated[c]
            if (!childCreated[c]) {
                create (c);
                #pragma flush childCreated[c]
                childCreated[c] = true;
            }
        }
    }
}
Foi útil?

Solução

Parece que seu padrão não está correto. Considere o caso quando o thread #1 é executado até depois do primeiro #pragma flush. Então o controle muda para o thread #2, que continua e cria um c, o controle é retomado pouco antes do segundo #pragma flush. Agora, o primeiro fio acorda e cria a criança novamente.

EDIT: Desculpe, errado: será incapaz de pegar a fechadura.

Edit 2: Não, ainda correto, porque o valor não será liberado no segmento nº 1

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