Padrão de simultaneidade do criador de logs no aplicativo multithread

8

O contexto: estamos trabalhando em um aplicativo multithread (Linux-C) que segue um modelo de pipeline.

Cada módulo possui um encadeamento privado e objetos encapsulados que processam dados; e cada estágio possui uma forma padrão de troca de dados com a próxima unidade.

O aplicativo está livre de vazamento de memória e é seguro para threads usando bloqueios no ponto em que eles trocam dados. O número total de threads é de cerca de 15 - e cada thread pode ter de 1 a 4 objetos. Criar cerca de 25 a 30 objetos estranhos, todos com alguns registros críticos a serem feitos.

A maioria das discussões que eu vi sobre diferentes níveis, como no Log4J e em outras traduções. A grande questão é sobre como o registro geral deve realmente acontecer?

Uma abordagem é toda exploração madeireira local faz fprintfpara stderr. O stderr é redirecionado para algum arquivo. Essa abordagem é muito ruim quando os logs se tornam muito grandes.

Se todos os objetos instanciarem seus loggers individuais - (cerca de 30 a 40 deles), haverá muitos arquivos. E, ao contrário do descrito acima, não se tem a ideia da verdadeira ordem dos eventos. O registro de data e hora é uma possibilidade - mas ainda é uma bagunça para agrupar.

Se houver um único padrão de logger global (singleton) - ele bloqueia indiretamente tantos threads enquanto um está ocupado colocando logs. Isso é inaceitável quando o processamento dos threads é pesado.

Então, qual deve ser a maneira ideal de estruturar os objetos de log? Quais são algumas das melhores práticas em aplicativos reais de larga escala?

Eu também adoraria aprender com alguns dos projetos reais de aplicativos de larga escala para obter inspirações!

======

EDITAR:

Com base nas duas respostas, aqui está a pergunta que me resta agora:

Qual é a melhor prática para atribuir loggers (filas de log) ao objeto: eles devem chamar algum global_api () ou o logger deve ser atribuído a eles no construtor. Quando os objetos estão em uma hierarquia profunda, essa abordagem posterior se torna tediosa. Se eles estão chamando algum global_api (), é meio que um acoplamento com o Aplicativo, portanto, tentar usar esse objeto em outro aplicativo gera essa dependência. Existe uma maneira mais limpa para isso?

Dipan Mehta
fonte
1
Ei, bem vindo de volta! Sentimos sua falta aqui e no local de trabalho.
11133 yannis

Respostas:

10

uma maneira aceitável de usar o log singleton que delega o log real para seu próprio encadeamento

você pode usar qualquer solução eficiente produtor / consumidor (como uma lista vinculada sem bloqueio baseada no CaS atômico) para reunir as mensagens de log sem se preocupar com o fato de ser um bloqueio global implícito

a chamada de log irá primeiro filtrar e criar a mensagem de log e depois passá-la para o consumidor, o consumidor pegará e gravará (e liberará os recursos da mensagem individual)

catraca arrepiante
fonte
Obrigado pela resposta. Internamente, eu estava pensando na mesma direção. Minha única pergunta é - se você deseja tornar um objeto de uso geral - não é limitativo o acesso a um global externo esperado do aplicativo? Você pode elaborar mais sobre como você será alocado por módulo e o logger global?
Dipan Mehta 11/10/12
6

A resposta de Ratchet Freak é o que inicialmente pensei também.

Um método alternativo pode ser fornecer a cada um dos seus módulos sua própria fila produtor-consumidor e, em seguida, fazer com que o mecanismo do seu logger verifique essas filas em seu próprio encadeamento.

Isso pode acabar sendo mais flexível, porque você pode alternar quantos criadores de logs você usa - você pode ter um para tudo, um para cada módulo ou dividir os módulos em grupos e ter um para cada grupo.

Edit: Elaboração

(não se importe com o meu C - foi o que você disse que está codificando, certo?)

Portanto, essa ideia está tendo uma fila / lista produtor-consumidor para cada um dos seus módulos. Essa fila provavelmente seria algo como isto:

enum LogItemType {INFO, ERROR};

struct LogItem
{
    LogItemType type;
    char *msg;
};

struct LogQueue {...}; // implementation details -- holds an array/list of LogItem

bool queueLogItem(log_queue *, log_item *);
bool queueHasItems(log_queue *);
bool queueIsFull(log_queue *);
LogItem *dequeueLogItem(log_queue *);

Cada módulo precisará inicializar sua própria fila ou ser passado pelo código de inicialização que configura os encadeamentos etc. O código de inicialização provavelmente deve manter referências a todas as filas:

LogQueue *module_queues = {module_1_queue_ptr, module_2_queue_ptr, ... , module_N_queue_ptr};

setModuleLoggingQueue(module1, module_1_queue_ptr);
// .
// .
// .

Dentro dos módulos, você deve criar LogItems e colocá-los na fila para cada mensagem.

LogItem *item = malloc(sizeof(LogItem));
item->type = INFO;
item->msg = malloc(MSG_SIZE)
memcpy("MSG", item->msg);
queueLogItem(module_queue, item);

Então, você teria um ou mais consumidores das filas que receberiam as mensagens e, na verdade, gravariam o log em um loop principal da seguinte maneira:

void loggingThreadBody()
{
    while (true)
    {
        for (i = 0; i < N; i++)
        {
            if (queueHasItems(module_queues[i]))
                writeLogItem(dequeueLogItem(module_queues[i]));
        }

        threadSleep(200);
    }
}

ou algo assim.

Isso seria flexível para permitir que você tenha diferentes consumidores das filas, por exemplo:

// For one logger:

LogQueue *module_queues = {module_1_queue_ptr, module_2_queue_ptr, ... , module_N_queue_ptr};

initLoggerThread(module_queues);


// -OR-
// For multiple loggers:

LogQueue *group1_queues = {module_1_queue_ptr, ..., module_4_queue_ptr};
LogQueue *group2_queues = {module_5_queue_ptr, ... , module_Nmin7_queue_ptr};
LogQueue *group3_queues = {module_Nmin7_queue_ptr, ... , module_N_queue_ptr};

initLoggerThread(group1_queues);
initLoggerThread(group2_queues);
initLoggerThread(group3_queues);

Nota: Suponho que você deseje alocar a memória para uma mensagem de log no momento da fila e desalocá-la no tempo de consumo (supondo que suas mensagens contenham conteúdo dinâmico).

Outra edição:

Esqueci de mencionar: se você espera muita atividade nos encadeamentos do módulo, poderá ver se é possível fazer gravações de log de forma assíncrona para que as coisas não bloqueiem.

Mais uma edição:

Você provavelmente também desejará colocar um registro de data e hora como parte do LogItem; com o (s) encadeamento (s) do criador de logs passando pelas filas sequencialmente, as instruções de log podem ficar fora de ordem a partir de quando ocorrerem cronologicamente nos módulos.

E ainda outra edição:

Com essa configuração, seria bastante fácil passar todos os módulos pela mesma fila e apenas fazer com que o consumidor olhasse para aquela fila, o que o levaria de volta à resposta do Ratchet Freak.

Nossa, você vai parar de editar!?:

Além disso, você pode ter uma fila para cada grupo de módulos e um criador de logs para cada fila.

Ok, eu vou parar agora.

Paulo
fonte
Isto é interessante. Você pode elaborar mais esse design?
Dipan Mehta 11/10/12
@DipanMehta certeza, mas não posso fazê-lo no momento. Vou tentar atualizá-lo hoje à noite - definitivamente até amanhã à noite.
paul
@DipanMehta Ok, eu atualizei minha resposta :)
paul
@DipanMehta Adicionada outra edição ....
paul
Obrigado Paul. Isso parece melhor e realmente responde à minha pergunta. Eu adicionei uma ligeira observação adicional na pergunta - deixe-me ver se alguém pode esclarecer isso.
Dipan Mehta 15/10/12