2

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;
}
John London
  • 1,250
  • 2
  • 14
  • 32
  • 2
    For the record, getting bent out of shape over program size is typically unnecessary. Hard disks are big and fast, programs are mapped into memory semi-lazily, such that the unused parts don't delay startup, etc. If it runs fast enough, adding a MB to the size of the executable doesn't usually matter (and I doubt `iostream` is adding quite that much, though on Windows, I can't be 100% sure how much it can offload to shared runtime libraries). – ShadowRanger Aug 23 '16 at 04:08
  • 1
    For simple hello world program with stdio.h, gnu gcc produces 8KB, mingw32 produces 30KB and mingw-w64 produces 59KB. But since you said even a difference of 1MB doesn't really matter, I should not worry about this at all? – John London Aug 23 '16 at 05:20
  • Yup. Even the slowest, most heavily fragmented spinning disk drive nowadays reads several MB/sec (and if it's not fragmented, it's dozens or hundreds of MB/sec). The smaller computers typically have ~64 GB of space, and most of them have 4-20x that much space; even assuming the OS and other apps use 32 GB, using 1 MB out of 32 GB of free space is not usually important unless you're literally compiling and installing hundreds or thousands of copies of your program in different places. :-) – ShadowRanger Aug 23 '16 at 05:27
  • Also see [Is cout synchronized/thread-safe?](https://stackoverflow.com/q/6374264/608639) – jww May 27 '18 at 11:36

1 Answers1

1

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.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • FYi, credit where credit is due, I borrowed the spec citation from [this answer](http://stackoverflow.com/a/6374525/364696), which is making it 100% clear that while synchronized standard iostream objects are guaranteed not to have data races (corrupted streams or dropped/doubled outputs), there is no guarantee at all about data ordering or interleaving. – ShadowRanger Aug 23 '16 at 04:22
  • I start learning programming through competitive programming, so that is why I am so reluctant to use iostream. With your suggestion to wrap std::mutex around stdio.h functions, it works properly now. I am surprised that the program doesn't increase in size much, probably because atomic contains mutex already. I learnt more than I expected through this random example. Thanks! – John London Aug 23 '16 at 05:15
  • @JohnLondon: It's not so much that `std::mutex` is replicated in `std::atomic`, it's that `std::mutex` is a fairly thin wrapper around the OS provided locking primitives, which are dynamically linked to your program, not statically linked. Most of the `std::mutex` behaviors basically boil down to defining a struct and then calling outside functions, with minimal logic hosted in your own executable. – ShadowRanger Aug 23 '16 at 05:20