如何轻松使std :: cout线程安全?

时间:2013-02-05 22:22:01

标签: c++ multithreading logging locking iostream

我有一个多线程应用程序,它大量使用std::cout进行日志记录而没有任何锁定。在这种情况下,如何轻松添加锁定机制以使std::cout线程安全?

我不想搜索std::cout的每次出现并添加一行锁定代码。这太乏味了。

有更好的做法吗?

10 个答案:

答案 0 :(得分:24)

虽然我无法确定这适用于std libs的每个编译器/版本 但在代码库中我使用std :: cout :: operator<<()它已经是线程安全的。

我假设您正在尝试做的事情是在与运算符连接时将std :: cout从混合字符串中停止<<每个字符串多次,跨多个线程。

字符串出现乱码的原因是因为有一个"外部"在运营商上竞赛<< 这可能导致这样的事情发生。

//Thread 1
std::cout << "the quick brown fox " << "jumped over the lazy dog " << std::endl;

//Thread 2
std::cout << "my mother washes" << " seashells by the sea shore" << std::endl;

//Could just as easily print like this or any other crazy order.
my mother washes the quick brown fox seashells by the sea sure \n
jumped of the lazy dog \n

如果是这样的话,那么答案要比制作自己的线程安全cout或实现与cout一起使用的锁更简单。

在将字符串传递给cout

之前,只需撰写字符串即可

例如。

//There are other ways, but stringstream uses << just like cout.. 
std::stringstream msg;
msg << "Error:" << Err_num << ", " << ErrorString( Err_num ) << "\n"; 
std::cout << msg.str();

这样你的叮咬就不会出现乱码,因为它们已经完全形成,而且在发送之前无论如何还要更好地完成你的弦乐。

答案 1 :(得分:17)

注意:这个答案是在C ++之前的20,因此它不会将std::osyncstream与其单独的缓冲一起使用,而是使用锁定。

我想你可以实现自己的类包装cout并将互斥量与它相关联。新课程的operator <<会做三件事:

  1. 为互斥锁创建一个锁,可能会阻塞其他线程
  2. 执行输出,即为包装的流和传递的参数
  3. 执行运算符<<
  4. 构造不同类的实例,将锁传递给
  5. 这个不同的类会将锁定和委托运算符<<保留到包装的流中。第二类的析构函数最终会破坏锁并释放互斥锁。

    因此,只要所有输出都通过具有相同互斥锁的对象传输,您作为单个语句编写的任何输出(即作为单个<<调用序列)都将以原子方式打印。

    让我们调用两个类synchronized_ostreamlocked_ostream。如果sync_coutsynchronized_ostream包裹std::cout的实例,那么序列

    sync_cout << "Hello, " << name << "!" << std::endl;
    

    将导致以下操作:

    1. synchronized_ostream::operator<<会获得锁
    2. synchronized_ostream::operator<<会将“Hello”的打印委托给cout
    3. operator<<(std::ostream&, const char*)会打印“你好”,
    4. synchronized_ostream::operator<<会构建一个locked_ostream并将锁定传递给
    5. locked_ostream::operator<<会将name的打印委托给cout
    6. operator<<(std::ostream&, std::string)会打印名称
    7. 对于感叹号和结束操纵器
    8. ,发生cout同一个代理
    9. locked_ostream临时被破坏,锁被释放

答案 2 :(得分:9)

我非常喜欢Nicolás在this question中给出的创建临时对象并将保护代码放在析构函数上的技巧。

std::cout

然后,您可以从任何线程将其用作常规PrintThread{} << "my_val=" << val << std::endl;

ostringstream

对象以常规#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char *pseudo = malloc(21); if (fgets(pseudo,21,stdin)) { char *cr = strrchr(pseudo, '\n'); if (cr == NULL) { int c; while ((c = fgetc(stdin)) != '\n' && c != EOF); } else { *cr = 0; } } else { *pseudo = 0; } char * tube = malloc(21); if (fgets(tube,21,stdin)) { char *cr = strrchr(tube, '\n'); if (cr == NULL) { int c; while ((c = fgetc(stdin)) != '\n' && c != EOF); } else { *cr = 0; } } else { *tube = 0; } printf("%s : %s\n", pseudo, tube); return 0; } 收集数据。一旦达到昏迷,对象就会被销毁并清除所有收集到的信息。

答案 3 :(得分:8)

C++20开始,您可以使用std::osyncstream包装器:

http://en.cppreference.com/w/cpp/io/basic_osyncstream

{
  std::osyncstream bout(std::cout); // synchronized wrapper for std::cout
  bout << "Hello, ";
  bout << "World!";
  bout << std::endl; // flush is noted, but not yet performed
  bout << "and more!\n";
} // characters are transferred and std::cout is flushed
  

它保证所有输出都是相同的最终结果   目标缓冲区(上例中的std :: cout)将是免费的   数据竞争并且不会以任何方式交错或乱码,只要很长时间   因为每次写入最终目标缓冲区都是通过   (可能是不同的)std :: basic_osyncstream的实例。

答案 4 :(得分:4)

可行的解决方案为每个线程使用行缓冲区。您可能会获得交错行,但不会交错。如果将其附加到线程本地存储,则还可以避免锁争用问题。然后,当一行已满(或如果你想要的话,则为flush),你将它写入stdout。最后一个操作当然必须使用锁。你将所有这些都填充到一个streambuffer中,你把它放在std :: cout和它的原始streambuffer之间。

这个问题无法解决的问题是格式标记(例如数字的十六进制/十进制/八进制),它们有时会在线程之间进行渗透,因为它们会附加到流中。这没什么不好,假设您只是记录而不是将其用于重要数据。它有助于不专门格式化。如果您需要某些数字的十六进制输出,请尝试:

template<typename integer_type>
std::string hex(integer_type v)
{
    /* Notes:
    1. using showbase would still not show the 0x for a zero
    2. using (v + 0) converts an unsigned char to a type
       that is recognized as integer instead of as character */
    std::stringstream s;
    s << "0x" << std::setfill('0') << std::hex
        << std::setw(2 * sizeof v) << (v + 0);
    return s.str();
}

类似的方法也适用于其他格式。

答案 5 :(得分:3)

为了快速调试c ++ 11应用程序并避免交错输出,我只需编写类似这样的小函数:

...
#include <mutex>
...
mutex m_screen;
...
void msg(char const * const message);
...
void msg(char const * const message)
{
  m_screen.lock();
  cout << message << endl;
  m_screen.unlock();
}

我将这些类型的函数用于输出,如果需要数值,我只需使用以下内容:

void msgInt(char const * const message, int const &value);
...
void msgInt(char const * const message, int const &value)
{
  m_screen.lock();
  cout << message << " = " << value << endl;
  m_screen.unlock();
}

这很简单,对我来说很好,但我不知道它是否在技术上是正确的。所以我很高兴听到你的意见。


好吧,我没有读到这个:

  

我不想搜索每次出现的std :: cout并添加一行锁定代码。

对不起。但是我希望它可以帮到某个人。

答案 6 :(得分:1)

根据Conchylicultor提出的答案,但没有继承std::ostringstream

编辑:修复了重载运算符的返回类型,并为std::endl添加了重载。

编辑1:我已将其扩展为simple header-only library,用于记录/调试多线程程序。

#include <iostream>
#include <mutex>
#include <thread>
#include <vector>    
#include <chrono>

static std::mutex mtx_cout;

// Asynchronous output
struct acout
{
        std::unique_lock<std::mutex> lk;
        acout()
            :
              lk(std::unique_lock<std::mutex>(mtx_cout))
        {

        }

        template<typename T>
        acout& operator<<(const T& _t)
        {
            std::cout << _t;
            return *this;
        }

        acout& operator<<(std::ostream& (*fp)(std::ostream&))
        {
            std::cout << fp;
            return *this;
        }
};

int main(void)
{


    std::vector<std::thread> workers_cout;
    std::vector<std::thread> workers_acout;

    size_t worker(0);
    size_t threads(5);


    std::cout << "With std::cout:" << std::endl;

    for (size_t i = 0; i < threads; ++i)
    {
        workers_cout.emplace_back([&]
        {
            std::cout << "\tThis is worker " << ++worker << " in thread "
                      << std::this_thread::get_id() << std::endl;
        });
    }
    for (auto& w : workers_cout)
    {
        w.join();
    }

    worker = 0;

    std::this_thread::sleep_for(std::chrono::seconds(2));

    std::cout << "\nWith acout():" << std::endl;

    for (size_t i = 0; i < threads; ++i)
    {
        workers_acout.emplace_back([&]
        {
            acout() << "\tThis is worker " << ++worker << " in thread "
                    << std::this_thread::get_id() << std::endl;
        });
    }
    for (auto& w : workers_acout)
    {
        w.join();
    }

    return 0;
}

输出:

With std::cout:
        This is worker 1 in thread 139911511856896
        This is worker  This is worker 3 in thread 139911495071488
        This is worker 4 in thread 139911486678784
2 in thread     This is worker 5 in thread 139911503464192139911478286080


With acout():
        This is worker 1 in thread 139911478286080
        This is worker 2 in thread 139911486678784
        This is worker 3 in thread 139911495071488
        This is worker 4 in thread 139911503464192
        This is worker 5 in thread 139911511856896

答案 7 :(得分:1)

我知道这是一个老问题,但它对我的问题帮助很大。我根据这篇帖子答案创建了一个实用工具类,我想分享我的结果。

考虑到我们使用C ++ 11或后面的C ++版本,此类提供print和println函数,以在调用标准输出流之前组合字符串并避免并发问题。这些是可变函数,它使用模板来打印不同的数据类型。

您可以在我的github上查看其在生产者 - 消费者问题中的用法:https://github.com/eloiluiz/threadsBar

所以,这是我的代码:

class Console {
private:
    Console() = default;

    inline static void innerPrint(std::ostream &stream) {}

    template<typename Head, typename... Tail>
    inline static void innerPrint(std::ostream &stream, Head const head, Tail const ...tail) {
        stream << head;
        innerPrint(stream, tail...);
    }

public:
    template<typename Head, typename... Tail>
    inline static void print(Head const head, Tail const ...tail) {
        // Create a stream buffer
        std::stringbuf buffer;
        std::ostream stream(&buffer);
        // Feed input parameters to the stream object
        innerPrint(stream, head, tail...);
        // Print into console and flush
        std::cout << buffer.str();
    }

    template<typename Head, typename... Tail>
    inline static void println(Head const head, Tail const ...tail) {
        print(head, tail..., "\n");
    }
};

答案 8 :(得分:0)

除了同步外,此解决方案还提供有关写入日志的线程的信息。

免责声明:这是一种幼稚的同步日志的方法,但是它可能适用于一些小的调试用例。

thread_local int thread_id = -1;
std::atomic<int> thread_count;

struct CurrentThread {

  static void init() {
    if (thread_id == -1) {
      thread_id = thread_count++;
    }
  }

  friend std::ostream &operator<<(std::ostream &os, const CurrentThread &t) {
    os << "[Thread-" << thread_id << "] - ";
    return os;
  }
};

CurrentThread current_thread;
std::mutex io_lock;
#ifdef DEBUG
#define LOG(x) {CurrentThread::init(); std::unique_lock<std::mutex> lk(io_lock); cout << current_thread; x << endl;}
#else
#define LOG(x)
#endif

可以这样使用。

LOG(cout << "Waiting for some event");

它将提供日志输出

[Thread-1] - Entering critical section 
[Thread-2] - Waiting on mutex
[Thread-1] - Leaving critical section, unlocking the mutex

答案 9 :(得分:0)

这是我使用自定义枚举和宏管理 std::cout 上的线程安全操作的方式:

enum SynchronisedOutput { IO_Lock, IO_Unlock };

inline std::ostream & operator<<(std::ostream & os, SynchronisedOutput so) {
  static std::mutex mutex;

  if (IO_Lock == so) mutex.lock();
  else if (IO_Unlock == so)
    mutex.unlock();

  return os;
}

#define sync_os(Os) (Os) << IO_Lock
#define sync_cout sync_os(std::cout)
#define sync_endl '\n' << IO_Unlock

这允许我写这样的东西:

sync_cout << "Hello, " << name << '!' << sync_endl;

在没有赛车问题的线程中。