John London John London - 3 months ago 22
C++ Question

printf calls messed up with std::thread but std::cout are fine

When printf (stdio.h) is used, "Start both threads\n" and "Enter a number\n" mixed together (something like "StEanterrt b a oth thnureaber\nads\n"), but this does not happen in std::cout (iostream). I suspect this has something to do with std::thread, but I am still new with multi-threading programming.

I really don't want to use iostream because it makes the program super large!

I am using mingw32 g++ 4.9.2, compile with

g++ -o foreback foreback.cpp -O2 -std=c++11
. (Although my computer is 64-bits, but I find out that mingw-w64 produces program about twice the size by mingw32, so I didn't use it).

//#define IOS
#ifdef IOS
#include <iostream>
#endif
#include <stdio.h>
#include <thread>
#include <atomic>
#include <windows.h> // for Sleep()

std::atomic<int> atom(1);

void foreground() {
int c = 1;
while (c) {
#ifdef IOS
std::cout << "Enter a number: ";
std::cin >> c;
#else
printf("Enter a number: ");
scanf("%d", &c);
#endif
atom.store(c, std::memory_order_relaxed);
}
}

void background() {
FILE *out = fopen("foreback.txt", "w");
int c = 1;
while (c) {
fprintf(out, "%d", c);
c = atom.load(std::memory_order_relaxed);
Sleep(500);
}
fclose(out);
}

int main() {
std::thread f(foreground), b(background);
#ifdef IOS
std::cout << "Start both threads.\n";
#else
printf("Start both threads.\n");
#endif
f.join();
b.join();
#ifdef IOS
std::cout << "End of both threads.\n";
#else
printf("End of both threads.\n");
#endif
return 0;
}

Answer

std::cout provides no guarantees on interleaving either; it's not mentioned in C++03, and C++11's FDIS says the following in §27.4.1 [iostream.objects.overview]:

Concurrent access to a synchronized (§27.5.3.4) standard iostream object’s formatted and unformatted input (§27.7.2.1) and output (§27.7.3.1) functions or a standard C stream by multiple threads shall not result in a data race (§1.10). [ Note: Users must still synchronize concurrent use of these objects and streams by multiple threads if they wish to avoid interleaved characters. — end note ]

That note at the end basically means "std::cout is allowed to interleave characters too". It's possible it isn't doing so due to the compiler/runtime library specific implementation in general, or due to the attempts to synchronize itself with stdio.h (turning off sync_with_stdio might cause it to begin interleaving again). But it's not a language guarantee; you're just lucking out.

If you want output to not interleave, you need to either perform all I/O from a single thread (have your workers take arguments and compute values, and the main thread is responsible for performing the I/O to output the computed values), or explicitly lock around all I/O functions that are used from different threads that target the same stream/FILE*. You could easily use stdio.h, you'd just need to have a std::mutex that you lock (e.g. with std::lock_guard) around your uses of stdio.h functions; as long as it's done consistently, you're guaranteed no interleaving.

Comments