Parallelitätsmuster des Loggers in einer Multithread-Anwendung

8

Der Kontext: Wir arbeiten an einer Multithread-Anwendung (Linux-C), die einem Pipeline-Modell folgt.

Jedes Modul verfügt über einen privaten Thread und gekapselte Objekte, die Daten verarbeiten. und jede Stufe hat eine Standardform des Datenaustauschs mit der nächsten Einheit.

Die Anwendung ist frei von Speicherverlusten und ist threadsicher, indem Sperren an dem Punkt verwendet werden, an dem sie Daten austauschen. Die Gesamtzahl der Threads beträgt ungefähr 15 und jeder Thread kann 1 bis 4 Objekte haben. Erstellen von etwa 25 bis 30 ungeraden Objekten, für die alle eine kritische Protokollierung erforderlich ist.

Die meisten Diskussionen habe ich über verschiedene Ebenen wie in Log4J und seinen anderen Übersetzungen gesehen. Die wirklich großen Fragen sind, wie die gesamte Protokollierung wirklich ablaufen soll.

Ein Ansatz ist , alle lokale Protokollierung tut fprintfzu stderr. Der stderr wird zu einer Datei umgeleitet. Dieser Ansatz ist sehr schlecht, wenn die Protokolle zu groß werden.

Wenn alle Objekte ihre einzelnen Logger instanziieren - (ungefähr 30-40 von ihnen), gibt es zu viele Dateien. Und anders als oben wird man nicht die Idee einer wahren Reihenfolge der Ereignisse haben. Zeitstempelung ist eine Möglichkeit - aber es ist immer noch ein Chaos, sie zusammenzustellen.

Wenn es ein einzelnes globales Logger-Muster (Singleton-Muster) gibt, blockiert es indirekt so viele Threads, während einer damit beschäftigt ist, Protokolle zu erstellen. Dies ist nicht akzeptabel, wenn die Verarbeitung der Threads schwer ist.

Was sollte also der ideale Weg sein, um die Protokollierungsobjekte zu strukturieren? Was sind einige der Best Practices in tatsächlichen Großanwendungen?

Ich würde auch gerne von einigen der realen Designs von Großanwendungen lernen, um Inspirationen zu erhalten!

======

BEARBEITEN:

Basierend auf beiden Antworten hier ist die Frage, die mir jetzt bleibt:

Was ist die beste Vorgehensweise beim Zuweisen von Loggern (Protokollierungswarteschlangen) zum Objekt: Sollten sie global_api () aufrufen oder sollte ihnen der Logger im Konstruktor zugewiesen werden. Wenn sich die Objekte in einer tiefen Hierarchie befinden, wird dieser spätere Ansatz mühsam. Wenn sie global_api () aufrufen, ist dies eine Art Kopplung mit der Anwendung. Wenn Sie also versuchen, dieses Objekt in einer anderen Anwendung zu verwenden, wird diese Abhängigkeit ausgelöst. Gibt es dafür einen saubereren Weg?

Dipan Mehta
quelle
1
Hey, willkommen zurück! Wir haben Sie sowohl hier als auch am Arbeitsplatz vermisst.
Yannis

Antworten:

10

Eine akzeptable Möglichkeit, den Singleton-Logger zu verwenden, der die eigentliche Protokollierung an einen eigenen Thread delegiert

Sie können dann jede effiziente Producer-Consumer- Lösung (wie eine nicht blockierende verknüpfte Liste basierend auf dem atomaren CaS) verwenden, um die Protokollnachrichten zu erfassen, ohne befürchten zu müssen, dass es sich um eine implizite globale Sperre handelt

Der Protokollaufruf filtert und erstellt dann zuerst die Protokollnachricht und leitet sie dann an den Verbraucher weiter. Der Verbraucher greift dann zu und schreibt sie aus (und gibt die Ressourcen der einzelnen Nachricht frei).

Ratschenfreak
quelle
Danke für die Antwort. Intern dachte ich in die gleiche Richtung. Meine einzige Frage ist: Wenn Sie ein Objekt zu einem allgemeinen Zweck machen möchten, ist es nicht einschränkend, dass es auf ein externes globales Element zugreift, das von der Anwendung erwartet wird? Können Sie näher erläutern, wie Sie pro Modul und dem globalen Logger zugewiesen werden?
Dipan Mehta
6

Die Antwort von Ratchet Freak ist das, woran ich anfangs auch gedacht habe.

Eine alternative Methode könnte darin bestehen, jedem Ihrer Module eine eigene Producer-Consumer-Warteschlange zuzuweisen und diese Warteschlangen dann von Ihrem Logger-Mechanismus in einem eigenen Thread scannen zu lassen.

Dies kann flexibler werden, da Sie die Anzahl der verwendeten Logger austauschen können - Sie haben möglicherweise einen für alles, einen für jedes Modul oder teilen die Module in Gruppen auf und haben einen für jede Gruppe.

Edit: Ausarbeitung

(Kümmere dich nicht um mein C - das hast du gesagt, dass du programmierst, oder?)

Diese Idee hat also eine Produzenten-Konsumenten-Warteschlange / Liste für jedes Ihrer Module. Eine solche Warteschlange wäre wahrscheinlich ungefähr so:

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

Jedes Modul muss entweder eine eigene solche Warteschlange initialisieren oder eine vom Initialisierungscode übergeben, der die Threads usw. einrichtet. Der Init-Code sollte wahrscheinlich Verweise auf alle Warteschlangen enthalten:

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

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

Innerhalb der Module müssen sie LogItems erstellen und für jede Nachricht in die Warteschlange stellen.

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

Dann hätten Sie einen oder mehrere Konsumenten der Warteschlangen, die die Nachrichten aufnehmen und das Protokoll tatsächlich in einer Hauptschleife wie folgt schreiben würden:

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

        threadSleep(200);
    }
}

oder etwas ähnliches.

Dies wäre flexibel, da Sie unterschiedliche Konsumenten der Warteschlangen haben können, z.

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

Hinweis: Ich vermute, Sie möchten den Speicher für eine Protokollnachricht zur Zeit der Warteschlange zuweisen und zur Verbrauchszeit freigeben (vorausgesetzt, Ihre Nachrichten enthalten dynamischen Inhalt).

Noch eine Bearbeitung:

Vergessen zu erwähnen: Wenn Sie viel Aktivität in Ihren Modulthreads erwarten, sehen Sie möglicherweise, ob Sie die Protokollschreibvorgänge asynchron ausführen können, damit die Dinge nicht blockieren.

Noch eine Änderung:

Möglicherweise möchten Sie auch einen Zeitstempel als Teil des LogItem einfügen. Wenn die Logger-Threads nacheinander die Warteschlangen durchlaufen, werden die Log-Anweisungen möglicherweise nicht mehr in der richtigen Reihenfolge angezeigt, sobald sie chronologisch in den Modulen aufgetreten sind.

Und noch eine Änderung:

Mit diesem Setup wäre es ziemlich einfach, alle Module in derselben Warteschlange zu übergeben und nur den Verbraucher auf diese eine Warteschlange zu schauen, was Sie zurück zu Ratchet Freaks Antwort führen würde.

Geeze, hörst du auf zu bearbeiten?

Sie können auch eine Warteschlange für jede Gruppe von Modulen und einen Logger für jede Warteschlange haben.

Ok, ich werde jetzt aufhören.

paul
quelle
Das ist interessant. Können Sie mehr von diesem Design ausarbeiten?
Dipan Mehta
@ DipanMehta sicher, aber ich kann das im Moment nicht. Ich werde versuchen, es heute Abend zu aktualisieren - definitiv bis morgen Abend.
Paul
@ DipanMehta Ok, ich habe meine Antwort aktualisiert :)
Paul
@ DipanMehta Eine weitere Bearbeitung hinzugefügt ....
Paul
Danke, Paul. Dies scheint am besten und beantwortet meine Frage wirklich. Ich habe der Frage eine kleine zusätzliche Bemerkung hinzugefügt - lassen Sie mich sehen, ob jemand Licht ins Dunkel bringen kann.
Dipan Mehta