如何在多个线程中使用printf()

use*_*743 16 c linux printf multithreading multiprocess

我正在实现一个使用不同内核的多线程程序,并且同时执行许多线程.每个线程都进行一次printf()调用,结果不可读.

我如何制作printf()原子,以便printf()一个线程中的printf()调用与另一个线程中的调用不冲突?

Jon*_*ler 19

POSIX规格

POSIX规范包括以下功能:

功能版本getc(),getchar(),putc(),并putchar()分别命名为getc_unlocked(),getchar_unlocked(),putc_unlocked(),并putchar_unlocked()应提供在功能上等同于原始版本,与他们就不需要在一个线程安全的方式来实现的例外.在flockfile()(或ftrylockfile())和受保护的范围内使用时,它们应是线程安全的funlockfile().当且仅当在调用线程拥有(FILE *)对象时调用它们时,这些函数可以安全地用在多线程程序中,就像成功调用flockfile()ftrylockfile()函数之后的情况一样.

这些功能的规范提到:

flockfile()et al 的规范包括一揽子要求:

引用(FILE *)对象的所有函数(名称_unlocked以其结尾的函数除外)的行为应该像在内部使用flockfile()并在funlockfile()内部获取这些(FILE *)对象的所有权一样.

这取代了此答案以前版本中的建议代码.POSIX标准还规定:

[ *lockfile()]函数的行为就像存在与每个(FILE *)对象关联的锁定计数一样.FILE *创建()对象时,此计数隐式初始化为零.FILE *当计数为零时,()对象被解锁.当计数为正时,单个线程拥有(FILE *)对象.flockfile()调用该函数时,如果计数为零或计数为正且调用者拥有(FILE *)对象,则计数应递增.否则,调用线程将被挂起,等待计数返回零.每次呼叫funlockfile()都应减少计数.这允许匹配调用flockfile()(或成功调用ftrylockfile())并funlockfile()嵌套.

还有字符I/O功能的规范:

格式化的输出函数在此处记录:

printf()规范中的一个关键条款是:

生成fprintf()printf()打印的字符就像fputc()被调用一样.

注意使用'似乎'.但是,每个printf()函数都需要应用锁,以便在多线程应用程序中控制对流的访问.一次只有一个线程可以使用给定的文件流.如果操作是用户级调用fputc(),则其他线程可以散布输出.如果操作是用户级调用,例如printf(),那么整个调用和对文件流的所有访问都得到有效保护,因此只有一个线程正在使用它,直到调用printf()返回.

在关于线程主题的POSIX的系统接口:一般信息部分中,它说:

2.9.1线程安全

本卷POSIX.1-2008定义的所有函数都应是线程安全的,但以下函数1不必是线程安全的.

...一系列不需要线程安全的函数...

...在getc_unlocked(),getchar_unlocked(),putc_unlocked(),和putchar_unlocked()除非调用线程拥有(功能不需要是线程安全FILE *的调用访问)对象,是一个成功的调用后的情况flockfile()ftrylockfile()功能.

实现应根据需要提供内部同步,以满足此要求.

豁免职能清单不包含fputcputcputchar(或printf()等).

解释

改写2017-07-26.

  1. 流上的字符级输出是线程安全的,除非在没有先锁定文件的情况下使用'unlocked'函数.
  2. 更高级的函数,例如在开始时在printf()概念上调用,这意味着POSIX定义的流输出函数在每次调用时也是线程安全的.flockfile()funlockfile()
  3. 如果你想组操作对单个线程文件流,你可以通过使用显式调用这样做flockfile(),并funlockfile()在相关的流(不带系统的使用干扰*lockfile()功能.

这意味着不需要为自己创建互斥或等效机制; 该实现提供了允许您printf()在多线程应用程序中控制对等人的访问的功能.

...之前的答案中的代码已删除,因为不再相关......


Gra*_*sus 15

为了不混合来自不同线程的输出,您需要确保一次只使用一个线程printf.要实现这一点,最简单的解决方案是使用a mutex.在开始时初始化mutex:

static pthread_mutex_t printf_mutex;
...
int main()
{
    ...
    pthread_mutex_init(&printf_mutex, NULL);
    ...
Run Code Online (Sandbox Code Playgroud)

然后创建一个包装器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);
}
Run Code Online (Sandbox Code Playgroud)

  • 另一种可能性是使用服务线程,其工作是与终端交互.其他线程可以将"打印作业"排入其中. (3认同)
  • 应该没有必要定义你自己的锁,因为已经有标准功能了;参见例如 https://www.gnu.org/software/libc/manual/html_node/Streams-and-Threads.html(它还声称 printf 本身保证是原子的。我不知道这是否正确.) (2认同)