0

Here is a little program that runs two for loops on separate threads:

#include <iostream>
#include <thread>

void print_one() {
for (int i = 0; i < 1000; i++) {
    std::cout << i << "\n";
    }
}

void print_two() {
for (int i = 0; i > -1000; i--) {
    std::cout << i << "\n";
    }
}

int main(){
    std::thread t1(print_one);
    std::thread t2(print_two);

    t1.join();
    t2.join();
}

The program follows a pattern in which one for loop runs about 30-400 times (usually about 30-200), after which the other loop gets its "turn" and does the same. The behavior continues until the program exits.

I left mutex out on purpose to show that it's not about locks. In all examples that I've seen on Youtube, the loops usually alternate after every 1-3 iterations. On my PC it's as if two threads simply can't run simultaneously, and one thread has to take time off while the other one is working. I'm not experiencing any performance issues in general, so it doesn't seem like hardware failure to me.

A thread not doing anything while another one has time to execute std::cout and add a new line hundreds of times in a row just doesn't sound right to me. Is this normal behavior?

I have a Ryzen 5 1600 processor, if that makes any difference.

boulder
  • 25
  • 2
  • 5
    That's normal behavior for a preemptive multi-tasking OS (which all modern main-stream OS's are). One process (or thread) runs for a little while, then the OS preempts is and let another process (or thread) run for a little while. – Some programmer dude Aug 18 '21 at 17:28
  • 2
    Also, on a modern multi-core CPU the OS usually schedules all threads it can to run on the same core, so caches don't have to be reloaded when threads are preempted. Scheduling different threads of the same process to run on different cores can actually *decrease* performance if the threads uses shared data. – Some programmer dude Aug 18 '21 at 17:30
  • 1
    you only have one terminal that can show the output, so they have to share it – 463035818_is_not_an_ai Aug 18 '21 at 17:33
  • 1
    Also note, regardless of whether or not what you are seeing is "normal" behavior, it is not _wrong_ behavior. If it's important for your two threads to take turns writing lines to `cout`, then it's up to you to make them communicate with each other and coordinate their activity to ensure that the writes happen in the "correct" order. But note! The more you require the threads in any program to operate in lock-step with each other, the less benefit you get from using threads. The whole point of threads is to allow different activities to proceed _independently_ of each other. – Solomon Slow Aug 18 '21 at 18:09
  • 2
    *I left mutex out on purpose...* The use of `std::cout` by both threads is a single resource that has state that has to maintain a coherent state, which is probably done through a mutex, or clever use of atomics. – Eljay Aug 18 '21 at 19:02

2 Answers2

3

A thread not doing anything while another one has time to execute std::cout and add a new line hundreds of times in a row just doesn't sound right to me. Is this normal behavior?

There is no other sensible possibility here. Each thread only needs to do a single integer increment before it needs access to the terminal, something that only one thread can access at a time.

There are only two other possibilities, and they're both obviously terrible:

  1. A single core runs the two threads, switching threads after every single output. This provides atrocious performance as 90+% of the time, the core is switching from one thread to the other.

  2. The threads run on two cores. Each core does a single increment, then waits for the other core to finish writing to the terminal, and then writes to the terminal. Each core spends at least half its time waiting for the other core to release the terminal. This takes up two cores and provides fairly poor performance because the threads are spending a lot of time stopping and starting each other.

What you are seeing is the best possible behavior. The only sensible thing to do is to allow each thread to run long enough that the cost of switching is drowned out.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
1

Answer to the first part:

The behaviour you observed is a normal behaviour.

Each thread gets a specific amount of time (to be executed on the core) that is dynamically decided by the OS, based on current situations.

Answer to the second part:

The cout operation is buffered.

Even if the calls to write (or whatever it is that accomplishes that effect in that particular implementation) are guaranteed to be mutually exclusive, the buffer might be shared by the different threads. This will quickly lead to corruption of the internal state of the stream. [1]

That is why you see only newline for some time.

The solution to this issue is using printf(). It's behaviour is atomic.

EDITS:

The point made about the printf() function that its behaviour is atomic is based on my experiments. The code at [2] demonstrates that the printf() function is atomic in nature.

You can actually test it.

References:

  1. https://stackoverflow.com/a/6374525/7422352
  2. https://www.researchgate.net/publication/351979208_Code_to_demonstrate_sequential_execution_of_2_parallel_regions_created_using_OpenMP_API
Deepak Tatyaji Ahire
  • 4,883
  • 2
  • 13
  • 35
  • 1
    `std::cout` is thread safe for C++11 and afterwards. So your **second** part is completely off the mark. There is no corruption or any undefined behaviour. – ALX23z Aug 18 '21 at 19:08
  • Also, while OS/compilers tend to guarantee that `printf` is thread-safe it is by no means implied by C++ or C languages. When `printf` was designed there were no mentions of threads or anything of the sort. – ALX23z Aug 18 '21 at 19:18
  • @ALX23z, then why the multiple newline behaviour was observed by the OP? – Deepak Tatyaji Ahire Aug 18 '21 at 19:21
  • he didn't observe any multiple new line behaviour. He observed that usually one thread run or the other but - not alternating line by line... not really sure what he expected. He definitely should have also observed to some degree that some lines were mangled composition from lines from two threads - that's because that `std::cout` while guarantees thread-safety - it doesn't say anything about order of printed characters from multiple threads. – ALX23z Aug 18 '21 at 19:33
  • "add a new line hundreds of times in a row" this is what means newline is printed for multiple number of times. As far as I understand. – Deepak Tatyaji Ahire Aug 18 '21 at 19:34
  • 2
    @ALX23z: In your comment, you wrote: `"Also, while OS/compilers tend to guarantee that printf is thread-safe it is by no means implied by C++ or C languages."` That statement is incorrect since C11. According to [§7.21.2 ¶7 and ¶8 of the ISO C standard](http://port70.net/~nsz/c/c11/n1570.html#7.21.2p7), access to all streams must be protected by a lock for the duration of the entire `printf` function call. At least that is my interpretation of the word "access". But I admit that it is possible to interpret it differently, by considering one `printf` function call performing several "accesses". – Andreas Wenzel Aug 18 '21 at 19:35
  • @DeepakTatyajiAhire read OPs previous paragraph. He did not mean that it was printing `\n` 100 times consequently (which would imply corrupted print). He meant that it printed new line with desired content - value of `i`. Only that the alterations between threads weren't as he expected. – ALX23z Aug 18 '21 at 21:29