Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does `std::osyncstream` manage the out stream?

I wonder how a std::osyncstream object prevents data race conditons? Does it lock some mutex?

I'm specifically talking about the below program:

#include <iostream>
#include <fstream>
#include <thread>
#include <syncstream>


void worker( const std::size_t startValue, const std::size_t stopValue, std::ostream& os )
{
    for ( auto idx { startValue }; idx < stopValue; ++idx )
    {
        std::osyncstream out { os };
        out << "thread: " << std::this_thread::get_id( ) << "; work: " << idx << '\n';
    }
}

void runThreads( std::ostream& os )
{
    std::jthread t1 { worker, 10000, 20000, std::ref( os ) };
    std::jthread t2 { worker, 20000, 30000, std::ref( os ) };
}

int main( )
{
    std::ofstream file { "out.txt" };
    runThreads( file );
}

The source of the above code can be viewed here. Although I have made slight modifications to make it better and safer.

This simple program prints 20,000 lines into a file without generating a messy output.

The possible output:

thread: 2; work: 10000
thread: 3; work: 20000
thread: 2; work: 10001
thread: 2; work: 10002
thread: 2; work: 10003
thread: 2; work: 10004
thread: 2; work: 10005
thread: 2; work: 10006
.
.
.

What's going on behind the scenes? How do these two threads communicate with each other? Do they have separate copies of syncstream object? How does this object (i.e. out) manage the output stream os?

like image 352
digito_evo Avatar asked Jun 11 '26 12:06

digito_evo


1 Answers

Does it lock some mutex?

Yes, indirectly. The std::basic_osyncstream class, of which osyncstream is a specialization of the form basic_osyncstream<char>, is derived from std::basic_ostream and will typically have just one 'extra' member, of the std::basic_syncbuf class. From cppreference:

Typical implementation of std::basic_osyncstream holds only one member: the wrapped std::basic_syncbuf.

It is that basic_syncbuf object that implements the output synchronization, preventing data races. Again, from cppreference (bolding mine):

Typical implementation of std::basic_syncbuf holds a pointer to the wrapped std::basic_streambuf, a boolean flag indicating whether the buffer will transmit its contents to the wrapped buffer on sync (flush), a boolean flag indicating a pending flush when the policy is to not emit on sync, an internal buffer that uses Allocator (such as std::string), and a pointer to a mutex used to synchronize emit between multiple threads accessing the same wrapped stream buffer (these mutexes may be in a hash map with pointers to basic_streambuf objects used as keys).

like image 117
Adrian Mole Avatar answered Jun 13 '26 02:06

Adrian Mole



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!