printf() не является потокобезопасной в C++17!
Всем здравия! В разных источниках я читал, что функция printf() является потокобезопасной и может вызываться разными потоками для бесконфликтного вывода на консоль (при условии корректной передачи параметров, разумеется). И действительно, с многопоточным выводом эта функция всегда прекрасно справлялась, чем я собственно пользовался... До тех пор, пока не понадобилось откомпилировать программу с поддержкой стандарта C++17! В общем, вот минимально воспроизводимый пример, иллюстрирующий суть проблемы:
#include <stdio.h>
#include <conio.h>
#include <atomic>
#include <windows.h>
#define NUM_THREADS 4
std::atomic<bool> g_abExit(false);
DWORD WINAPI thread_func (LPVOID num)
{
while (!g_abExit.load(std::memory_order_acquire))
{
printf("*** THREAD #%02u: This is the bug, it's not a feature!\n",(UINT)num);
Sleep(1);
}
return 0;
}
int main()
{
HANDLE threads[NUM_THREADS];
printf("Enter q to quite...\n");
Sleep(500);
for (int i=0; i<NUM_THREADS; ++i)
if (!(threads[i] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)thread_func,(LPVOID)i,0,NULL))) return -1;
while (getch()!='q');
g_abExit.store(true,std::memory_order_release);
for (int i=0; i<NUM_THREADS; ++i)
WaitForSingleObject(threads[i],INFINITE);
return 0;
}
Пользуюсь компилятором MinGW GCC версии 9.2.0 (build 2). Компилирую для стандарта C++ по умолчанию (уж не знаю, какой стандарт использует MinGW по умолчанию, не нашёл как посмотреть) и получаю ожидаемо корректный результат:
Enter q to quite...
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
Однако, при компиляции с опцией -std=c++17, вся потокобезопасность вывода рушится, получается каша:
Enter q to quite...
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a f*** THREAD #00: This is the bug, it's not a feature!
eature*** THREAD #!
01: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a featu*** THREAD #re!
01: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a featu*** THREAD #03: This is the bre!
ug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #*03: This is the bug, it's not a feature!
** THREAD #02: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #0*1:** THThisR iEAD #00s the bu: This is the bg, ugit, it's not a fea's not a feature!
ture!
*** THREAD #02: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
*** THREAD #00: This is the bug, it's not a feature!
*** THREAD #01: This is the bug, it's not a feature!
*** THREAD #03: This is the bug, it's not a feature!
Поясните мне, пожалуйста, почему так происходит? Начиная с какой версии стандарта C++ функция printf() перестала быть потокобезопасной и почему? И есть ли способ вернуть ей потокобезопасность без написания собственного варианта-костыля (скажем, опция компилятора, делающая консольный вывод thread safe)?
P.S. Прочитал про функции flockfile(), funlockfile(), попробовал их, однако MinGW пишет, что они есть "undeclared". Хотя их использование - тоже так себе вариант, всё таки хочется, чтобы блокировкой занималась сама printf()...
Ответы (2 шт):
Нигде в стандарте и книжках не сказано, что результат printf не может перемешиваться. Гарантируется только то, что ничего не будет испорчено и программа не упадет. Хотите, чтобы все печаталось строго подряд - вешайте мьютекс (дорого по времени в некоторых случаях) или делайте более сложную систему с буферизацией.
Спасибо большое за разъяснения! Что ж, получается, что кроме как написать свой вариант printf() с мьютексом (или критической секцией), ничего не остаётся...
/*
* File: mt_printf.cpp
* Author: Light Shadow
*/
#include <stdio.h>
#include <cstdarg>
#include "windows.h"
#include "mt_printf.h"
CRITICAL_SECTION g_abMtPrintfCritSec;
void __attribute__ ((constructor)) _init_mt_printf()
{
InitializeCriticalSection(&g_abMtPrintfCritSec);
}
void __attribute__ ((destructor)) _final_mt_printf()
{
DeleteCriticalSection(&g_abMtPrintfCritSec);
}
int mt_printf(const char* format, ...)
{
va_list args;
va_start(args,format);
EnterCriticalSection(&g_abMtPrintfCritSec);
int res = vprintf(format,args);
LeaveCriticalSection(&g_abMtPrintfCritSec);
va_end(args);
return res;
}
Наверное, не слишком быстрый способ, но для отладочных целей сойдёт.