0

I use std::cout to print the log on console. Since the program is multi-thread, the print result would be disordered, if I use more than one "<<" operate after cout.


For example if one thread executes cout<< "A" << "B" << endl; Another thread might executes cout << "C"; between A and B. The result would be "ACB".


Hence I'm going to write a new class to inherit ostream(which is basic_ostream<char, char_traits<char>> in fact) and add a lock when the cout is initialized, so the print out should follow the proper order.

shum
  • 3
  • 2
  • 2
    Possible duplicate of [How to easily make std::cout thread-safe?](https://stackoverflow.com/questions/14718124/how-to-easily-make-stdcout-thread-safe) – Alan Birtles Apr 03 '19 at 07:09
  • 1
    @molbdnilo That still doesn't guarantee two concurrent output operations won't interleave characters! – BoBTFish Apr 03 '19 at 07:25
  • 1
    @BoBTFish I consider this my monthly reminder to Read The Standard and not rely on Knowing Things That Somebody Told Me. – molbdnilo Apr 03 '19 at 07:36
  • As noted as an answer to the duplicate question at [How to easily make std::cout thread-safe?](https://stackoverflow.com/q/14718124/158371): if you use C++20 you can use [osyncstream](http://en.cppreference.com/w/cpp/io/basic_osyncstream) – Christian Severin Apr 03 '19 at 08:01

3 Answers3

2

One option would be to create a class that holds a reference to a stream, but holds a lock throughout it's lifetime. Here's a simple example:

#include <iostream>
#include <mutex>

struct LockedOstream {
    std::lock_guard<std::mutex> lg_;
    std::ostream& os_;
    LockedOstream(std::mutex& m, std::ostream& os)
      : lg_{m}
      , os_{os}
    { }
    std::ostream& stream() const { return os_; }
};

int main()
{
    std::mutex m;
    LockedOstream(m, std::cout).stream() << "foo " << "bar\n";
    //                        ^ locked now                   ^ unlocked now
}

This works as long as all the printing that forms a single "unit" of output all occurs in the same statement.


Edit: Actually, the inheritance version is a lot nicer than I originally expected:

#include <iostream>
#include <mutex>

class LockedOstream : public std::ostream {
    static std::mutex& getCoutMutex()
        // use a Meyers' singleton for the cout mutex to keep this header-only
    {
        static std::mutex m;
        return m;
    }

    std::lock_guard<std::mutex> lg_;

  public:
    // Generic constructor
    // You need to pass the right mutex to match the stream you want
    LockedOstream(std::mutex& m, std::ostream& os)
      : std::ostream(os.rdbuf())
      , lg_{m}
    { }

    // Cout specific constructor
    // Uses a mutex singleton specific for cout
    LockedOstream()
      : LockedOstream(getCoutMutex(), std::cout)
    { }
};

int main()
{
    LockedOstream() << "foo " << "bar\n";
    //            ^ locked now          ^ unlocked now
}

As an aside:

(though the latter is sometimes contentious, but at least it's a good idea to know and make an informed choice).

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
BoBTFish
  • 19,167
  • 3
  • 49
  • 76
  • 1
    I made a small edit to the statement about when the printing is handled correctly. The lock gets released at the end of the statement that created it, regardless of how many lines are in that statement. Feel free to reword if my wording seems clumsy. +1. – Pete Becker Apr 03 '19 at 14:30
  • @PeteBecker Thanks, that's a fair point. I was going for "easy to understand" over "rigidly correct", but precise language is a good thing. – BoBTFish Apr 03 '19 at 14:47
2

You can define your own function

template<typename... Ts>
void locked_print(std::ostream& stream, Ts&&... ts)
{
    static std::mutex mtx;
    std::lock_guard<std::mutex> guard(mtx);
    (stream << ... << std::forward<Ts>(ts));
}

And when you want to be sure it's exclusive, you can call it like locked_print(std::cout, 1, 2, "bar");

Zereges
  • 5,139
  • 1
  • 25
  • 49
1

Since outstream << x1 << x2 << ... are multiple function calls, there is no way you can do that internally, besides locking everything until destruction of that same outstream. You can just force your constraint when you call it:

{
  std::lock_guard<std::mutex> guard(global_mutex);
  // your print here
}
Thomas Lang
  • 770
  • 1
  • 7
  • 17
  • I used cout a lot. It would be tedious to add lock_guard guard(global_mutex) every time I used cout. I'm not allowed to use printf neither, since it is C style. – shum Apr 03 '19 at 07:10