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

  1. Вывод на уровне символов в потоке является потокобезопасным, если только не используются "разблокированные" функции без предварительной блокировки файла.
  2. Функции более высокого уровня, такие как printf() концептуально вызов flockfile() в начале funlockfile() в конце, это означает, что функции вывода потока, определенные в POSIX, также являются поточно-ориентированными для каждого вызова.
  3. Если вы хотите сгруппировать операции в файловом потоке для одного потока, вы можете сделать это, явно используя вызовы 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;
      }
   }
Другие вопросы по тегам