Em um aplicativo multithreading, dois ou mais threads paralelos usam um ponteiro para chamar uma função virtual. Ao mesmo tempo, em outro thread, esse ponteiro muda para outro objeto. Isso é seguro ou devo tomar medidas para torná-lo seguro?
Considere o código:
class Scene {
public:
virtual void render() const = 0;
virtual void update() const = 0;
};
class D1: public Scene {
// members
public:
virtual void render() const { /* work with D1 members */ }
virtual void update() const { /* work with D1 members */ }
};
class D2: public Scene {
// members
public:
virtual void render() const { /* work with D2 members */ }
virtual void update() const { /* work with D2 members */ }
};
// …somewhere in thread 1
Scene *scene=new D1;
…
Scene *scene=new D2;
// … somewhere in thread 2
scene->render();
// … somewhere in thread 3
scene->update();
Por que considero arriscado
Se a chamada de função virtual não for uma operação atômica, posso imaginar a situação em que o compilador leva o ponteiro para o objeto (para ser passado implicitamente é this ) em uma etapa e se refere à chamada de função virtual em outra etapa, lendo novamente esse ponteiro da memória . Entre essas etapas o ponteiro pode ser alterado em outro thread e isso pode acabar com o método D2::render chamado para o objeto D1.
Eu nem tenho certeza de que tornar esse ponteiro volátil ajudaria o compilador a corrigir isso, já que é uma chance de o compilador usar essa dica para outros propósitos.
Minha solução simples
Uma solução simples para isso seria usar uma cópia intermediária:
// … somewhere in thread 2
Scene* safe_copy_scene = scene;
safe_copy_scene->render();
A questão é: é necessário ou a chamada de função virtual pode ser considerada atômica nesta perspectiva e isso é garantido por padrão e não depende de compilador/otimização?
Não, não é seguro fazer o ponteiro apontar para outro objeto sem sincronização e sim, você deve tomar medidas para torná-lo seguro para threads.
Uma maneira é torná-lo atômico:
Observe, porém, que se vários threads estiverem chamando
const
funções não membros no mesmoScene
objeto, você também precisará torná-los thread-safe.Você também precisa manter referências às cenas para que elas possam ser destruídas adequadamente quando você terminar com elas e elas não devem ser destruídas antes que qualquer thread que possa acessá-las tenha a garantia de não fazer mais tais acessos.
Você pode então mudar a cena ativa com:
Não é seguro, é uma corrida de dados e resulta em comportamento indefinido.
Imagine um cenário tão ruim:
A solução alternativa não é segura, porque o compilador pode otimizar a
safe_copy_scene
variável.Isso é seguro:
As operações atômicas sequenciadas com barreiras não permitirão a leitura
vtbl
préviascene
dethis
.