Как я могу создать новый класс, чтобы наследовать ostream и использовать его как cout, но с блокировкой

Я использую std::cout для печати журнала на консоли. Поскольку программа многопоточная, результат печати будет неупорядоченным, если я использую более одной операции "‹‹" после cout.


Например, если один поток выполняет cout<< "A" << "B" << endl;, другой поток может выполнять cout << "C"; между A и B. Результатом будет "ACB".


Поэтому я собираюсь написать новый класс, наследующий ostream (который на самом деле является basic_ostream<char, char_traits<char>>) и добавить блокировку при инициализации cout, поэтому распечатка должна следовать правильному порядку.


person shum    schedule 03.04.2019    source источник
comment
Возможный дубликат Как легко сделать std::cout потокобезопасным?   -  person Alan Birtles    schedule 03.04.2019
comment
@molbdnilo Это все еще не гарантирует, что две параллельные операции вывода не будут чередовать символы!   -  person BoBTFish    schedule 03.04.2019
comment
@BoBTFish Я считаю это своим ежемесячным напоминанием о том, чтобы прочитать стандарт и не полагаться на знание того, что мне кто-то сказал.   -  person molbdnilo    schedule 03.04.2019
comment
Как указано в ответе на повторяющийся вопрос в Как легко сделать std::cout потокобезопасным?: если вы используете C++20 вы можете использовать osyncstream   -  person Christian Severin    schedule 03.04.2019


Ответы (3)


Одним из вариантов было бы создание класса, который содержит ссылку на поток, но удерживает блокировку на протяжении всего времени его существования. Вот простой пример:

#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
}

Это работает до тех пор, пока вся печать, которая формирует единую «единицу» вывода, происходит в одном и том же операторе.


Редактировать: На самом деле, версия наследования намного лучше, чем я изначально ожидал:

#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
}

Как в сторону:

(хотя последнее иногда вызывает споры, но, по крайней мере, полезно знать и делать осознанный выбор).

person BoBTFish    schedule 03.04.2019
comment
Я внес небольшую правку в заявление о том, когда печать обрабатывается правильно. Блокировка снимается в конце инструкции, которая ее создала, независимо от того, сколько строк в этой инструкции. Не стесняйтесь перефразировать, если моя формулировка покажется вам неуклюжей. +1. - person Pete Becker; 03.04.2019
comment
@PeteBecker Спасибо, это справедливо. Я стремился к простому для понимания, а не к строго правильному, но точный язык - это хорошо. - person BoBTFish; 03.04.2019

Вы можете определить свою собственную функцию

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));
}

И если вы хотите убедиться, что это эксклюзив, вы можете назвать это как locked_print(std::cout, 1, 2, "bar");

person Zereges    schedule 03.04.2019

Поскольку outstream << x1 << x2 << ... — это множественные вызовы функций, у вас нет возможности сделать это внутри, кроме как заблокировать все до уничтожения того же исходящего потока. Вы можете просто заставить свое ограничение, когда вы его называете:

{
  std::lock_guard<std::mutex> guard(global_mutex);
  // your print here
}
person Thomas Lang    schedule 03.04.2019
comment
Я часто использовал cout. Было бы утомительно добавлять lock_guard‹std::mutex› guard(global_mutex) каждый раз, когда я использую cout. Мне также не разрешено использовать printf, так как это стиль C. - person shum; 03.04.2019