Как использовать printf() в нескольких потоках
Я реализую многопоточную программу, которая использует разные ядра, и многие потоки выполняются одновременно. Каждый поток вызывает вызов printf()
, и результат не читается.
Как я могу сделать printf()
atomic, так что вызов printf()
в одном потоке не конфликтует с вызовом printf()
в другом?
Ответы
Ответ 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);
}
Ответ 2
Спецификации POSIX
Спецификация POSIX включает следующие функции:
Версии функций getc()
, getchar()
, putc()
и putchar()
, соответственно, называются getc_unlocked()
, getchar_unlocked()
, putc_unlocked()
и putchar_unlocked()
, которые функционально эквивалентны оригинальные версии, за исключением того, что они не должны быть реализованы полностью поточно-безопасным образом. Они должны быть потокобезопасными при использовании в области, защищенной flockfile()
(или ftrylockfile()
) и funlockfile()
. Эти функции можно безопасно использовать в многопоточной программе тогда и только тогда, когда они вызываются, когда вызывающий поток владеет объектом (FILE *
), как это имеет место после успешного вызова функций flockfile()
или ftrylockfile()
.
В спецификации этих функций упоминается:
Спецификация для flockfile()
и др. включает требование к одежде:
Все функции, ссылающиеся на объекты (FILE *
), за исключением тех, которые имеют имена, заканчивающиеся на _unlocked
, должны вести себя так, как если бы они использовали flockfile()
и funlockfile()
внутри, чтобы получить право собственности на эти объекты (FILE *
).
Это заменяет предлагаемый код в предыдущих выпусках этого ответа. В стандарте POSIX также указывается:
Функции [*lockfile()
] должны вести себя так, как если бы был подсчет блокировки, связанный с каждым объектом (FILE *
). Этот счет неявно инициализируется до нуля, когда создается объект (FILE *
). Объект (FILE *
) разблокируется, когда счетчик равен нулю. Когда счетчик положителен, одному потоку принадлежит объект (FILE *
). Когда вызывается функция flockfile()
, если счетчик равен нулю или если счетчик положителен, а вызывающий пользователь имеет объект (FILE *
), счетчик должен быть увеличен. В противном случае вызывающий поток приостанавливается, ожидая, пока счетчик вернется к нулю. Каждый вызов funlockfile()
должен уменьшать счетчик. Это позволяет сопоставлять вызовы flockfile()
(или успешные вызовы ftrylockfile()
) и funlockfile()
для вложенности.
Существуют также спецификации функций ввода-вывода символов:
Отформатированные выходные функции описаны здесь:
Одним из ключевых условий в спецификации 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()
.
Реализации должны обеспечивать внутреннюю синхронизацию по мере необходимости для удовлетворения этого требования.
Список исключенных функций не содержит fputc
или putc
или putchar
(или printf()
и др.).
Интерпретация
Переписан 2017-07-26.
- Выход на уровне символов в потоке потокобезопасен, если не использовать "разблокированные" функции без предварительной блокировки файла.
- Функции более высокого уровня, такие как
printf()
концептуально вызывают flockfile()
в начале a funlockfile()
в конце, что означает, что функции вывода потока, определенные POSIX, также не требуют потоков для каждого вызова.
- Если вы хотите группировать операции в потоке файлов для одного потока, вы можете сделать это, явно используя вызовы
flockfile()
и funlockfile()
в соответствующем потоке (не мешая использованию системы *lockfile()
функции.
Это означает, что вам не нужно создавать мьютексы или эквивалентные механизмы для себя; реализация обеспечивает функции, позволяющие вам контролировать доступ к printf()
и др. в многопоточном приложении.
... Код из предыдущего ответа удален как уже не релевантный...
Ответ 3
Для linux здесь код для u в c: 3 потоках, выполняющихся на разных ядрах, печатает привет мир, не конфликтуя друг с другом придворным замком.
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <syscall.h>
#include <sys/types.h>
void * printA ( void *);
void * printB ( void *);
void * printC ( void *);
pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
int main(int argc, char *argv[]) {
int error;
pthread_t tid1, tid2,tid3;
if ( error = pthread_create (&tid1, NULL, printA, NULL ))
{
fprintf (stderr, "Failed to create first thread: %s\n",strerror(error));
return 1;
}
if ( error = pthread_create (&tid2, NULL, printB, NULL ))
{
fprintf (stderr, "Failed to create second thread: %s\n",strerror(error));
return 1;
}
if ( error = pthread_create (&tid3, NULL, printC, NULL ))
{
fprintf (stderr, "Failed to create second thread: %s\n",strerror(error));
return 1;
}
if (error = pthread_join(tid1, NULL))
{
fprintf (stderr, "Failed to join first thread: %s\n",strerror(error));
return 1;
}
if (error = pthread_join(tid2, NULL))
{
fprintf (stderr, "Failed to join second thread: %s\n",strerror(error));
return 1;
}
if (error = pthread_join(tid3, NULL))
{
fprintf (stderr, "Failed to join second thread: %s\n",strerror(error));
return 1;
}
return 0;
}
void * printA ( void *arg )
{
if ( error = pthread_mutex_lock( &mylock ))
{
fprintf (stderr, "Failed to acquire lock in printA: %s\n",strerror(error));
return NULL;
}
printf("Hello world\n");
if ( error = pthread_mutex_unlock( &mylock ))
{
fprintf (stderr, "Failed to release lock in printA: %s\n",strerror(error));
return NULL;
}
}
void * printB ( void *arg )
{
int error;
if ( error = pthread_mutex_lock( &mylock ))
{
fprintf (stderr, "Failed to acquire lock in printB: %s\n",strerror(error));
return NULL;
}
printf("Hello world\n");
if ( error = pthread_mutex_unlock( &mylock ))
{
fprintf (stderr, "Failed to release lock in printA: %s\n",strerror(error));
return NULL;
}
}
void * printC ( void *arg )
{
int error;
if ( error = pthread_mutex_lock( &mylock ))
{
fprintf (stderr, "Failed to acquire lock in printB: %s\n",strerror(error));
return NULL;
}
printf("Hello world\n");
if ( error = pthread_mutex_unlock( &mylock ))
{
fprintf (stderr, "Failed to release lock in printA: %s\n",strerror(error));
return NULL;
}
}