web-dev-qa-db-ja.com

マルチスレッドアプリケーションでのロガーの同時実行パターン

コンテキスト:パイプラインモデルに従うマルチスレッド(Linux-C)アプリケーションに取り組んでいます。

各モジュールには、データの処理を行うプライベートスレッドとカプセル化されたオブジェクトがあります。各ステージには、次のユニットとデータを交換する標準形式があります。

アプリケーションはメモリリークがなく、データを交換するポイントでロックを使用してスレッドセーフです。スレッドの総数は約15で、各スレッドには1〜4個のオブジェクトを含めることができます。約25〜30の奇妙なオブジェクトを作成します。これらのオブジェクトにはすべて、重要なロギングが必要です。

私が見たほとんどの議論は、Log4Jのようなさまざまなレベルとそれに関する他の翻訳です。本当に大きな問題は、全体的なロギングが実際にどのように行われるべきかについてです。

1つのアプローチは、すべてのローカルロギングがfprintfからstderrを実行することです。 stderrはいくつかのファイルにリダイレクトされます。ログが大きくなりすぎると、このアプローチは非常に悪くなります。

すべてのオブジェクトが個々のロガーをインスタンス化する場合(そのうちの約30〜40)、ファイルが多すぎます。また、上記とは異なり、イベントの真の順序についての考えはありません。タイムスタンプは1つの可能性ですが、照合するのはまだ面倒です。

グローバルロガー(シングルトン)のパターンが1つある場合、ログの作成でビジー状態のときに、非常に多くのスレッドが間接的にブロックされます。スレッドの処理が重い場合、これは受け入れられません。

では、ロギングオブジェクトを構造化するための理想的な方法は何でしょうか。実際の大規模アプリケーションでのベストプラクティスにはどのようなものがありますか?

また、インスピレーションを得るために、大規模アプリケーションの実際のデザインのいくつかから学びたいと思っています!

======

編集:

ここでの両方の答えに基づいて、私は今残っている質問です:

ロガー(ロギングキュー)をオブジェクトに割り当てる際のベストプラクティスは何ですか?それらがglobal_api()を呼び出すか、ロガーがコンストラクターでロガーに割り当てられるかどうかです。オブジェクトが深い階層にある場合、この後のアプローチは面倒になります。彼らがいくつかのglobal_api()を呼び出している場合、それはアプリケーションとの一種のカップリングであるため、他のアプリケーションでこのオブジェクトを使用しようとすると、この依存関係がスローされます。これのためのよりきれいな方法はありますか?

8
Dipan Mehta

実際のロギングを独自のスレッドに委任するシングルトンロガーを使用する許容可能な方法

次に、効率的な producer-consumer ソリューション(アトミックCaSに基づく非ブロッキングリンクリストのような)を使用して、暗黙的なグローバルロックであることを心配せずにログメッセージを収集できます。

次に、ログ呼び出しは最初にログメッセージをフィルタリングして作成し、それをコンシューマーに渡します。コンシューマーは、それを取得して書き込みます(そして個々のメッセージのリソースを解放します)。

10
ratchet freak

ラチェット・フリークの答えは私が最初に考えたものでもあります。

別の方法として、各モジュールに独自のプロデューサー/コンシューマーキューを割り当て、ロガーメカニズムにこれらのキューを独自のスレッドでスキャンさせる方法があります。

使用するロガーの数を切り替えることができるため、これはより柔軟になる可能性があります-すべてに1つ、各モジュールに1つ、またはモジュールをグループに分割して各グループに1つ持つことができます。

編集:詳細

(私のCを気にしないでください-それはあなたがコーディングしているとあなたが言ったことですよね?)

したがって、このアイデアは、モジュールごとにプロデューサー/コンシューマーキュー/リストを持っています。このようなキューは、おそらく次のようになります。

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

各モジュールは、そのような独自のキューを初期化するか、スレッドなどを設定する初期化コードによって渡される必要があります。initコードは、おそらくすべてのキューへの参照を保持する必要があります。

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

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

モジュール内では、LogItemを作成して、メッセージごとにキューに入れます。

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

次に、メッセージを受け取り、実際に次のようにメインループでログの書き込みを行うキューの1つ以上のコンシューマーが存在します。

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

        threadSleep(200);
    }
}

またはそのようなもの。

これにより、キューのさまざまなコンシューマを使用できるようになります。たとえば、次のようになります。

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

注:キュー時にログメッセージ用のメモリを割り当て、消費時にメモリの割り当てを解除する必要があると思います(メッセージに動的コンテンツが含まれている場合)。

別の編集:

言及を忘れた:モジュールスレッドで多くのアクティビティを期待している場合は、ログ書き込みを非同期で実行して、ブロックされないようにできるかどうかを確認できます。

まだ編集:

また、LogItemの一部としてタイムスタンプを配置することもできます。ロガースレッドがキューを順番に通過する場合、ログステートメントは、モジュールで発生順に発生したときから順番が狂うことがあります。

そしてもう一つの編集:

この設定を使用すると、すべてのモジュールに同じキューを渡し、消費者にその1つのキューを表示させるだけでかなり簡単になり、ラチェットフリークの答えに戻ることができます。

Geeze、編集を中止しますか!?:

また、モジュールのグループごとに1つのキューを用意し、各キューにロガーを用意することもできます。

よし、やめよう。

5
paul