Como sincronizar o acesso a uma variável global com leituras muito freqüentes / gravações muito raras?

9

Estou trabalhando na infraestrutura de registro de depuração para um aplicativo de servidor. Cada ponto de registro no código-fonte especifica seu nível (CRITICAL, ERROR, etc.) entre outros parâmetros. Então, no ponto de registro do código fonte, aparece como:

DBG_LOG_HIGH( … )

que é uma macro que se expande para

if ( CURRENT_DEBUG_LOG_LEVEL >= DEBUG_LOG_LEVEL_HIGH ) {
   // prepare and emit log record
}

em que DEBUG_LOG_LEVEL_HIGH é uma constante predefinida (digamos 2) e CURRENT_DEBUG_LOG_LEVEL é alguma expressão avaliada para o nível atual de registro de depuração definido pelo usuário. A abordagem mais simples seria definir CURRENT_DEBUG_LOG_LEVEL como:

extern int g_current_debug_log_level;
#define CURRENT_DEBUG_LOG_LEVEL (g_current_debug_log_level)

Eu gostaria de permitir que o usuário altere o nível atual de log de depuração durante a execução do aplicativo, e está tudo bem se a alteração levar alguns segundos para entrar em vigor. O aplicativo é multiencadeado e as alterações em g_current_debug_log_level podem ser facilmente serializadas (por exemplo, por CRITICAL_SECTION ), mas para não impactar a expressão de desempenho ( CURRENT_DEBUG_LOG_LEVEL >= DEBUG_LOG_LEVEL_HIGH ) deve ser executado o mais rápido possível, por isso evito usar qualquer thread mecanismo de sincronização lá.

Então, minhas perguntas são:

  1. A ausência de sincronização em g_current_debug_log_level faz com que o valor incorreto seja lido? Embora não deva afetar a exatidão do aplicativo, pois o usuário poderia ter definido o nível atual de log de depuração para o valor incorreto, isso afetaria o desempenho do aplicativo, pois poderia emitir um volume muito alto de log de depuração por um período de tempo incontrolável. >

  2. Minha solução garantirá que a alteração no nível atual de registro de depuração atingirá todos os segmentos após o período de tempo aceitável (digamos, alguns segundos)? Idealmente, eu gostaria que a operação de mudança de nível fosse síncrona, de modo que quando o usuário recebesse reconhecimento na operação de mudança de nível, ela pudesse contar com o registro subseqüente a ser emitido de acordo com o novo nível.

Eu também apreciaria muito as sugestões para implementações alternativas que satisfaçam os requisitos acima (impacto mínimo no desempenho para comparação de nível e mudança de nível síncrono com latência de não mais do que alguns segundos).

    
por Sergey Kleyman 30.09.2011 в 20:50
fonte

6 respostas

4

Não há nada que exija que uma gravação feita em um thread em um núcleo jamais se torne visível para outro thread lendo em outro núcleo, sem fornecer algum tipo de fence para criar uma borda 'acontece antes' entre a gravação e a leitura .

Portanto, para ser estritamente correto, você precisaria inserir as instruções de barreira / barreira de memória apropriadas após a gravação no nível de log e antes de cada leitura. As operações de cerca não são baratas, mas são mais baratas que um mutex completo.

Na prática, porém, dado um aplicativo concorrente que está usando bloqueio em outro lugar, e o fato de que seu programa continuará a operar mais ou menos corretamente se a gravação não se tornar visível, é provável que a gravação se torne visível devido a outras operações de esgrima dentro de um curto espaço de tempo e atender às suas necessidades. Então você provavelmente pode simplesmente escrever e pular as cercas.

Mas o uso de um cercamento adequado para impor o que acontece antes da aresta é realmente a resposta correta. O FWIW, C ++ 11 fornece um modelo de memória explícita que define a semântica e expõe esses tipos de operações de proteção no nível da linguagem. Mas até onde eu sei, nenhum compilador ainda implementa o novo modelo de memória. Então, para C / C ++, você precisa usar o bloqueio de uma biblioteca ou de um fence explícito.

    
por acm 30.09.2011 / 21:08
fonte
1

Supondo que você esteja no Windows, o Windows só roda no x86 (que é a maioria verdade por enquanto, mas pode mudar ...), e assumindo que apenas um thread grava na variável, você pode sair sem fazer nenhuma sincronização qualquer que seja.

Para estar "correto", você deve usar um bloqueio de um leitor / gravador de alguma forma.

    
por R.. 30.09.2011 / 21:02
fonte
0

Dada sua implementação atual, sugiro que você dê uma olhada nas operações atômicas. Se isso for destinado apenas ao Windows, consulte Acesso variável intertravado

    
por NuSkooler 30.09.2011 / 21:10
fonte
0

Veja os novos bloqueios do Slim Reader / Writer disponíveis no Vista e 7. Eles devem fazer o que você quiser com o mínimo de sobrecarga possível:

link

    
por Mahmoud Al-Qudsi 30.09.2011 / 22:34
fonte
0

Em x86 e x64, o volátil impõe pouquíssimos custos diretos. Pode haver alguns custos indiretos relacionados a forçar a re-busca de variáveis não relacionadas (acessos a variáveis voláteis são tratados como cercas de memória no nível do compilador para todas as outras variáveis de 'endereço endereçado'). Pense em uma variável volátil, como uma chamada de função, em que o compilador perderá informações sobre o estado da memória na chamada.

No Itanium, o volátil tem algum custo, mas não é tão ruim. No ARM, o compilador MSVC assume como padrão não fornecer barreiras (e não fornecer ordenação) para materiais voláteis.

Uma coisa importante é que deve haver pelo menos um acesso a essa variável de nível de log em seu programa, caso contrário, ela pode ser transformada em uma saída constante e otimizada. Isso poderia ser um problema se você pretendia definir a variável por meio de nenhum mecanismo diferente do depurador.

    
por Neeraj Singh 10.05.2012 / 09:56
fonte
0

Defina a variável ordinal visível no escopo e atualize-a conforme apropriado (quando o nível de log for alterado) Se os dados estiverem alinhados corretamente (ou seja, um padrão), você não precisará de nada especial, exceto declarar sua variável de log atual como "volátil". Isso funcionaria para tamanho LONG (ordinal de 32 bits). Então, sua solução seria:

extern volatile long g_globalLogLevel;

Não há necessidade de sincronização externa (por exemplo, RWlock / CriticalSection / Spin etc)

    
por Valeri Atamaniouk 19.05.2012 / 23:44
fonte