Как использовать 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;
      }
   }