Как использовать printf() в нескольких потоках
Я реализую многопоточную программу, которая использует разные ядра, и многие потоки выполняются одновременно. Каждая нить делает printf()
вызов, и результат не читается.
Как я могу сделать printf()
атомный, так что printf()
вызов в одном потоке не конфликтует с printf()
позвонить по другому?
3 ответа
POSIX Технические характеристики
Спецификация POSIX включает в себя следующие функции:
getc_unlocked()
getchar_unlocked()
putc_unlocked()
putchar_unlock()
Версии функций
getc()
,getchar()
,putc()
, а такжеputchar()
соответственно названныйgetc_unlocked()
,getchar_unlocked()
,putc_unlocked()
, а такжеputchar_unlocked()
Должны быть предоставлены, которые функционально эквивалентны оригинальным версиям, за исключением того, что они не должны быть реализованы полностью потокобезопасным способом. Они должны быть поточно-ориентированными при использовании в области, защищеннойflockfile()
(или жеftrylockfile()
) а такжеfunlockfile()
, Эти функции могут безопасно использоваться в многопоточной программе, если и только если они вызываются, в то время как вызывающий поток владеет (FILE *
), как это происходит после успешного вызоваflockfile()
или жеftrylockfile()
функции.
Спецификация для этих функций упоминает:
flockfile()
ftrylockfile()
funlockfile()
Спецификация для flockfile()
и др. включает общее требование:
Все функции, которые ссылаются (
FILE *
) объекты, кроме тех, чьи имена заканчиваются на_unlocked
, должны вести себя так, как будто они используютflockfile()
а такжеfunlockfile()
внутренне, чтобы получить право собственности на эти (FILE *
) объекты.
Это заменяет предложенный код в предыдущих выпусках этого ответа. Стандарт POSIX также определяет:
[
*lockfile()
] функции должны вести себя так, как будто с каждой из них связан счетчик блокировок (FILE *
) объект. Этот счет неявно инициализируется нулем, когда (FILE *
) объект создан. (FILE *
) объект разблокируется, когда счетчик равен нулю. Когда число положительно, один поток владеет (FILE *
) объект. Когдаflockfile()
Функция вызывается, если счетчик равен нулю или если счетчик положителен, и вызывающему принадлежит (FILE *
) объект, счет должен быть увеличен. В противном случае вызывающий поток должен быть приостановлен, ожидая, пока счетчик вернется к нулю. Каждый звонокfunlockfile()
уменьшит счет. Это позволяет сопоставлять звонкиflockfile()
(или успешных звонковftrylockfile()
) а такжеfunlockfile()
быть вложенным.
Есть также спецификации для функций ввода / вывода символов:
getc()
getchar()
putc()
putchar()
fgetc()
fputc()
Форматированные выходные функции описаны здесь:
printf()
Одним из ключевых положений в 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()
в началеfunlockfile()
в конце, это означает, что функции вывода потока, определенные в POSIX, также являются поточно-ориентированными для каждого вызова. - Если вы хотите сгруппировать операции в файловом потоке для одного потока, вы можете сделать это, явно используя вызовы
flockfile()
а такжеfunlockfile()
в соответствующем потоке (без вмешательства в использование системой*lockfile()
функции.
Это означает, что нет необходимости создавать мьютексы или эквивалентные механизмы для себя; реализация обеспечивает функции, позволяющие вам контролировать доступ к printf()
и др. в многопоточном приложении.
... Код из предыдущего ответа удален как неактуальный...
Чтобы не смешивать выходы из разных потоков, необходимо убедиться, что используется только один поток 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);
}
Для linux, вот код для вас в потоках 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;
}
}