Pattern di concurrency del logger nell'applicazione multithread

8

Il contesto: Stiamo lavorando su un'applicazione multi-thread (Linux-C) che segue un modello di pipeline.

Ogni modulo ha un thread privato e oggetti incapsulati che eseguono l'elaborazione dei dati; e ogni fase ha una forma standard di scambio di dati con l'unità successiva.

L'applicazione è priva di perdite di memoria ed è protetta da un blocco tramite i blocchi nel punto in cui vengono scambiati i dati. Il numero totale di thread è di circa 15 e ciascun thread può contenere da 1 a 4 oggetti. Fare circa 25 - 30 oggetti dispari che hanno tutti un registro critico da fare.

La maggior parte delle discussioni ho visto su diversi livelli come in Log4J e sono altre traduzioni. Le vere domande più importanti riguardano il modo in cui la registrazione complessiva dovrebbe realmente accadere?

Un approccio è tutto il logging locale fa fprintf a stderr . Lo stderr viene reindirizzato su un file. Questo approccio è molto negativo quando i registri diventano troppo grandi.

Se tutti gli oggetti istanziano i loro logger individuali (circa 30-40 di questi) ci saranno troppi file. E a differenza di quanto sopra, non si avrà l'idea del vero ordine degli eventi. Il timestamp è una possibilità - ma è ancora un casino da raccogliere.

Se esiste un unico modello di logger globale (singleton), blocca in modo indiretto tanti thread mentre uno è occupato a caricare i registri. Questo è inaccettabile quando l'elaborazione dei thread è pesante.

Quindi quale dovrebbe essere il modo ideale per strutturare gli oggetti di logging? Quali sono alcune delle migliori pratiche nelle attuali applicazioni su larga scala?

Mi piacerebbe anche imparare da alcuni dei progetti reali di applicazioni su larga scala per ottenere ispirazione da!

======

EDIT:

In base a entrambe le risposte, ecco la domanda che mi rimane ora:

Qual è la migliore pratica sull'assegnazione dei logger (code di registrazione) all'oggetto: dovrebbero chiamare alcuni global_api () o il logger dovrebbe essere loro assegnato nel costruttore. Quando gli oggetti sono in una gerarchia profonda questo approccio successivo diventa noioso. Se richiamano alcuni global_api () è un tipo di accoppiamento con l'applicazione, quindi provare a utilizzare questo oggetto in un'altra applicazione genera questa dipendenza. C'è un modo più pulito per questo?

    
posta Dipan Mehta 11.10.2012 - 14:52
fonte

2 risposte

10

un modo accettabile per utilizzare il registratore singleton che delega la registrazione effettiva al proprio thread

puoi quindi utilizzare qualsiasi soluzione efficace produttore-consumatore (come un elenco collegato non bloccante basato sul atomic CaS) per raccogliere i messaggi di log senza preoccuparsi che si tratti di un blocco globale implicito

la chiamata di registro quindi prima filtrerà e costruirà il messaggio di log e poi lo passerà al consumatore, il consumatore lo prenderà e lo scriverà (e libererà le risorse del singolo messaggio)

    
risposta data 11.10.2012 - 15:12
fonte
5

La risposta di Ratchet Freak è ciò a cui inizialmente pensavo anch'io.

Un metodo alternativo potrebbe essere quello di dare a ciascuno dei tuoi moduli la propria coda produttore-consumatore e quindi fare in modo che il meccanismo del registratore analizzi queste code sul proprio thread.

Questo potrebbe risultare più flessibile perché puoi cambiare quanti logger usi - potresti averne uno per tutto, uno per ciascun modulo, o dividere i moduli in gruppi e averne uno per ogni gruppo.

Modifica: elaborazione

(non importa il mio C - è quello che hai detto che stai codificando, giusto?)

Quindi questa idea è avere una coda / lista produttore-consumatore per ciascuno dei tuoi moduli. Una tale coda sarebbe probabilmente una cosa del genere:

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 *);

Ogni modulo dovrà inizializzare la propria coda o passarne uno dal codice di inizializzazione che imposta i thread, ecc. Il codice init dovrebbe probabilmente mantenere i riferimenti a tutte le code:

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

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

All'interno dei moduli dovrai creare LogItems e metterli in coda per ciascun messaggio.

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

Quindi, avresti uno o più consumatori delle code che prenderebbero i messaggi e in realtà faranno scrivere il log in un ciclo principale in questo modo:

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

        threadSleep(200);
    }
}

o qualcosa del genere.

Ciò sarebbe flessibile nel consentire di avere utenti diversi delle code, ad esempio:

// 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: suppongo che tu voglia allocare la memoria per un messaggio di log in coda e deallocarla al momento del consumo (supponendo che i tuoi messaggi conterranno contenuti dinamici).

Un'altra modifica:

Hai dimenticato di menzionare: se ti aspetti un sacco di attività sui thread del modulo potresti vedere se puoi scrivere il log in modo asincrono, quindi le cose non si bloccano.

Ancora un'altra modifica:

Probabilmente vorrai anche inserire un timestamp come parte del LogItem; con i thread del logger che passano attraverso le code in sequenza, le istruzioni del log potrebbero andare fuori ordine a partire dal momento in cui si sono verificate cronologicamente nei moduli.

E ancora un'altra modifica:

Con questa configurazione sarebbe abbastanza facile passare tutti i moduli nella stessa coda e avere solo il consumatore che guarda quella coda, il che ti riporterà alla risposta di Ratchet Freak.

Geeze, smetterai di modificare!?:

Inoltre potresti avere una coda per ogni gruppo di moduli e disporre di un logger per ogni coda.

Ok, mi fermo ora.

    
risposta data 11.10.2012 - 17:16
fonte

Leggi altre domande sui tag