web-dev-qa-db-ja.com

複数のスレッドでprintf()を使用する方法

異なるコアを使用するマルチスレッドプログラムを実装しており、多くのスレッドが同時に実行されます。各スレッドはprintf()呼び出しを行い、結果は読み取り可能ではありません。

あるスレッドのprintf()呼び出しが別のスレッドのprintf()呼び出しと競合しないように、printf()をアトミックにするにはどうすればよいですか?

17
user3242743

POSIX仕様

POSIX仕様には、次の関数が含まれています。

関数getc()getchar()putc()putchar()のバージョンで、それぞれgetc_unlocked()getchar_unlocked()putc_unlocked()、およびputchar_unlocked()は、完全にスレッドセーフな方法で実装する必要がないことを除いて、元のバージョンと機能的に同等です。 flockfile()(またはftrylockfile())とfunlockfile()で保護されたスコープ内で使用すると、スレッドセーフになります。これらの関数は、flockfile()への呼び出しが成功した後のように、呼び出しスレッドが(FILE *)オブジェクトを所有しているときに呼び出された場合にのみ、マルチスレッドプログラムで安全に使用できます。またはftrylockfile()関数。

これらの関数の仕様は次のとおりです。

flockfile()らの仕様には、ブランケット要件が含まれています。

FILE *)オブジェクトを参照するすべての関数は、名前が_unlockedで終わるものを除き、内部でflockfile()およびfunlockfile()を使用してこれらの所有権を取得するかのように動作します。 (FILE *)オブジェクト。

これは、この回答の以前の版で推奨されていたコードに取って代わります。 POSIX標準では、次のことも指定しています。

[*lockfile()]関数は、各(FILE *)オブジェクトに関連付けられたロックカウントがあるかのように動作します。 (FILE *)オブジェクトが作成されると、このカウントは暗黙的にゼロに初期化されます。 (FILE *)オブジェクトは、カウントがゼロのときにロック解除されます。カウントが正の場合、単一のスレッドが(FILE *)オブジェクトを所有します。 flockfile()関数が呼び出されたときに、カウントがゼロであるか、カウントが正であり、呼び出し元が(FILE *)オブジェクトを所有している場合、カウントは増分されます。それ以外の場合、呼び出しスレッドは中断され、カウントがゼロに戻るのを待ちます。 funlockfile()を呼び出すたびに、カウントが減少します。これにより、flockfile()への一致する呼び出し(またはftrylockfile()への成功した呼び出し)およびfunlockfile()をネストすることができます。

文字I/O関数の仕様もあります。

フォーマットされた出力関数は次のとおりです。

printf()仕様の重要な規定の1つは次のとおりです。

fprintf()およびprintf()によって生成された文字は、fputc()が呼び出されたかのように印刷されます。

のように使用されていることに注意してください。ただし、ロックを適用してマルチスレッドアプリケーションでストリームへのアクセスを制御するには、各printf()関数が必要です。特定のファイルストリームを使用できるのは、一度に1つのスレッドだけです。操作がfputc()へのユーザーレベルの呼び出しである場合、他のスレッドが出力に点在する可能性があります。操作がprintf()などのユーザーレベルの呼び出しである場合、呼び出し全体とファイルストリームへのすべてのアクセスが効果的に保護されるため、printf()への呼び出しが戻るまで、1つのスレッドのみがそれを使用します。 。

Threads の件名に関するPOSIXのSystem Interfaces:General Informationセクションのセクションには、次のように記載されています。

2.9.1スレッドセーフ

このボリュームのPOSIX.1-2008で定義されているすべての関数は、スレッドセーフである必要があります。

…スレッドセーフである必要のない関数のリスト…

getc_unlocked()getchar_unlocked()putc_unlocked()、およびputchar_unlocked()関数は、呼び出し元のスレッドが(FILE *)を所有していない限り、スレッドセーフである必要はありませんflockfile()関数またはftrylockfile()関数の呼び出しが成功した後の場合と同様に、呼び出しによってアクセスされるオブジェクト。

実装は、この要件を満たすために、必要に応じて内部同期を提供する必要があります。

免除された関数のリストには、fputcまたはputcまたはputchar(またはprintf()など)が含まれていません。

解釈

2017-07-26を書き直しました。

  1. 最初にファイルをロックせずに「ロック解除」機能を使用しない限り、ストリームの文字レベルの出力はスレッドセーフです。
  2. printf()などの上位レベルの関数は、概念的には最初にflockfile()を呼び出し、最後にfunlockfile()を呼び出します。つまり、POSIX定義のストリーム出力関数もスレッドセーフです。コール。
  3. 単一スレッドのファイルストリームでの操作をグループ化する場合は、関連するストリームでflockfile()およびfunlockfile()への呼び出しを明示的に使用することでグループ化できます(システムの使用を妨げることなく、 *lockfile()関数。

つまり、ミューテックスや同等のメカニズムを自分で作成する必要はありません。この実装は、マルチスレッドアプリケーションでのprintf()などへのアクセスを制御できるようにする関数を提供します。

…以前の回答からのコードはもはや関連がないとして削除されました…

19

異なるスレッドからの出力を混合しないようにするには、一度に1つのスレッドのみがprintfを使用するようにする必要があります。これを実現するための最も簡単な解決策は、mutexを使用することです。最初にmutexを初期化します。

static pthread_mutex_t printf_mutex;
...
int main()
{
    ...
    pthread_mutex_init(&printf_mutex, NULL);
    ...

次に、printfのラッパーを作成して、mutexを取得したスレッドのみがprintfを呼び出すことができるようにします(それ以外の場合は、mutexまでブロックする必要があります。利用可能です) :

int sync_printf(const char *format, ...)
{
    va_list args;
    va_start(args, format);

    pthread_mutex_lock(&printf_mutex);
    vprintf(format, args);
    pthread_mutex_unlock(&printf_mutex);

    va_end(args);
}
17
Grapsus