1.研究背景

  在测试时发现mingw版本的gcc编译出来的程序,一个主程序新建20个线程,每个线程都循环向cout输出信息,几分钟程序就崩了,而用msvc和gcc-linaro版gcc交叉编译器编译出来的运行很久都没问题。

2.相关查询

2.1 C++ iostreams: Unexpected but legal multithreaded behaviour

https://berthub.eu/articles/posts/iostreams-unexpected/

2.1.1Multi-threading

One reason for using C++ is that it supports multi-threading (or more broadly, multi-processing) very well. The original C++ standard had no words on it because back in the day, officially there were no threads. Later versions of C++ (starting with C++ 2011) dusted off the iostreams specification and added words on thread safety.

This starts off with the following:

Concurrent access to a stream object (30.8, 30.9), stream buffer object (30.6), or C Library stream (30.12) by multiple threads may result in a data race (6.8.2) unless otherwise specified (30.4). [ Note: Data races result in undefined behavior (6.8.2). — end note ] – [iostreams.threadsafety]

This is a blanket statement that bad things may happen if we do stuff to iostreams from several threads at the same time, unless there is a specific statement that says doing so is safe.

Luckily, there is the following paragraph too:

Concurrent access to a synchronized (27.5.3.4) standard iostream object’s formatted and unformatted input (27.7.2.1) and output (27.7.3.1) functions or a standard C stream by multiple threads shall not result in a data race (1.10). [Note: Users must still synchronize concurrent use of these objects and streams by multiple threads if they wish to avoid interleaved characters. — end note] – [iostream.objects.overview]

No disasters will happen on concurrent use of iostreams, although if you print out two log lines to cerr at the same time, you may find them interleaved in your output. This certainly is not pretty & hard to parse, but at least it is not illegal.

Note however that this paragraph talks only about ‘synchronized’ streams. Once we call the much recommended sync_with_stdio(false), our streams are no longer synchronized, not only not with stdio, but not at all. This means every write operation on cin or cout etc must now be protected by a mutex.

This itself is likely reason enough to never call sync_with_stdio(false) in any multi-threaded program using cout to print things.

2.1.2 Basic thread locking in C++11

Notice that the requirement not to produce a data race applies only to the standard iostream objects (cout, cin, cerr, clog, wcout, wcin, wcerr, and wclog) and only when they are synchronized (which they are by default and which can be disabled using the sync_with_stdio member function).

Unfortunately I've noticed two phenomena; implementations either provide stricter guarantees than required (e.g., thread synchronization for all stream objects no matter what, giving poor performance) or fewer (e.g., standard stream objects that are sync_with_stdio produce data races). MSVC seems to lean toward the former while libc++ leans toward the latter.

Anyway, as the note indicates, you have to provide mutual exclusion yourself if you want to avoid interleaved characters. Here's one way to do it:

std::mutex m;

struct lockostream {
std::lock_guard<std::mutex> l;
lockostream() : l(m) {}
}; std::ostream &operator<<(std::ostream &os, lockostream const &l) {
return os;
} std::cout << lockostream() << "Hello, World!\n";

This way a lock guard is created and lives for the duration of the expression using std::cout. You can templatized the lockostream object to work for any basic_*stream, and even on the address of the stream so that you have a seperate mutex for each one.

Of course the standard stream objects are global variables, so you might want to avoid them the same way all global variables should be avoided. They're handy for learning C++ and toy programs, but you might want to arrange something better for real programs.

2.1.3 or U could do like this

You have to use the normal locking techniques as you would do with any other resource otherwise you are experiencing UB.

std::mutex m;
std::lock_guard<std::mutex> lock(m);
std::cout << "hello hello"; 

or alternativly you can use printf which is threadsafe(on posix):

printf("hello hello");

2.1.4 Summarising

Be very careful when using std::ios_base::sync_with_stdio(false), and if you do, also issue cin.tie(nullptr). Make sure sync_with_stdio is called before doing any i/o.

In general, be very weary of doing output operations on a single iostream from multiple threads - it may not do what you want.

Some further reading:

  • The libstdc++ bug I filed about this, where it will likely be concluded this is (unfortunately) not a bug, but intended behaviour
  • The {fmt} library is a simpler alternative to rapidly output text. Typically faster than printf.

2.2 Simple Lock-free std::cout in C++ Multithreading

https://wasin.io/blog/17_simple-lock-free-std-cout-cpp-multithreading.html

Whenever you need to do a quick multithreading program in C++, most of the time printing something out via std::cout::operator<< to validate the logic is the most go-to solution.

Whenever at least two threads call std::cout::operator<< at the same time, then console result will probably be mess, not what we exepct. Newline might not get printed, space sometimes included but other time not included, etc.

Apply full std::mutex seems to be overkill. Anyway mutex solution is not lock-free. What’s about std::atomic? Real close, but it still doesn’t guarantee lock-free solution for us. So those two methods go out of the way.

The sane solution is to use std::atomic_flag. Lower level than std::atomic. It’s comparable to std::atomic<bool> but without load and store operation. See the following code

    static std::atomic_flag lock = ATOMIC_FLAG_INIT;

    // spin-lock (suitable if short time waiting is known beforehand)
while (lock.test_and_set(std::memory_order_acquire))
; std::cout << "Print something\n"; // release the lock
lock.clear();

Check ThreadLocal.cpp for full example of multiple threads trying to print something out at the same time.

Compile it with g++ -std=c++11 ThreadLocal.cpp -lpthread.

3.总结

由于默认情况下,sync_with_stdio是true的,标准定义多线程时的cout行为为UB(undefined behavior),所以不同编译器出现不同的现象也并不奇怪。

多线程环境下的cout建议还是使用atomic_flag或者std::lock_guard<std::mutex>方式,加锁实现时c++20的osyncstream 也是不错的方案。

多线程std::cout 深入研究的更多相关文章

  1. std::cout和printf

    禁止std::cout和printf混用,在多线程环境下可能导致coredump. 说明:printf和std::cout分别为标准c语言与c++中的函数,两者的缓冲区机制不同(printf无缓冲区, ...

  2. c/c++ 多线程 std::call_once的应用

    多线程 std::call_once的应用 std::call_once的应用:类成员的延迟初始化,并只初始化一次.和static的作用很像,都要求是线程安全的,c++11之前在多线程的环境下,sta ...

  3. c/c++ 多线程 std::call_once

    多线程 std::call_once 转自:https://blog.csdn.net/hengyunabc/article/details/33031465 std::call_once的特点:即使 ...

  4. C++11并发——多线程std::thread (一)

    https://www.cnblogs.com/haippy/p/3284540.html 与 C++11 多线程相关的头文件 C++11 新标准中引入了四个头文件来支持多线程编程,他们分别是< ...

  5. C++ 多线程 std::thread 使用总结

    在C++ 11之前,官方并没有支持线程库.C++ 11通过标准库引入了对 thread 类的支持,大大方便了完成多线程开发的工作. std::thread 构造函数  (1)thread() noex ...

  6. Visual Studio将std::cout输出到Output窗口

    在debug的时候,输出到Output需要使用OutputDebugString函数,但部分库的log是采用std::cout输出的,需要用控制台(黑窗)程序来查看输出.有没有一种使用GUI和Outp ...

  7. std::cout彩色输出

    Mac OS效果 Windows 效果 想写这个东西其实是因为最近要写个命令行的工具,但是有个问题是什么呢?就是传统的那个黑漆漆的窗口看起来很蛋疼.并且完全看不到重点,于是就想起 来这么一个东西.相对 ...

  8. 02 - 用wxStreamToTextRedirector和wxTextCtrl输出std::cout

    遇到问题,单行显示, new line丢失 原因: wxTextCtrl默认是单行的 解决办法:使用wxTE_MULTILINE参数初始化wxTextCtrl wxTextCtrl *text = , ...

  9. c/c++ 多线程 std::lock

    多线程 std::lock 当要同时操作2个对象时,就需要同时锁定这2个对象,而不是先锁定一个,然后再锁定另一个.同时锁定多个对象的方法:std::lock(对象1.锁,对象2.锁...) 额外说明: ...

随机推荐

  1. 没有学历如何从事Java开发?

    学历成了当今社会一个衡量一个人能力的标准,未来只会越来越深入,也有的人说不要总是把学历挂嘴边,学历并不能代表能力,确实学历不能代表能力,但是学历是能代表一个的人学习深度,也是在职场上必备的一个敲门砖. ...

  2. PHP key() 函数

    ------------恢复内容开始------------ 实例 从当前内部指针位置返回元素键名: <?php$people=array("Peter","Joe ...

  3. PHP acos() 函数

    实例 返回不同数的反余弦: <?phpecho(acos(0.64) . "<br>");echo(acos(-0.4) . "<br>&q ...

  4. Spring与Mybatis整合占位符无法解析问题

    问题:写了一个新的dao接口,进行单元测试时提示: Initialization of bean failed; nested exception is org.springframework.bea ...

  5. 【SDOI2010】猪国杀 题解(模拟)

    前言:嗅到了一丝头秃的味道…… ------------------ 题目链接 题目实在太长,变量也很多.建议至少读个三五遍再做题.不要忽略任何细节,不要想当然.(因为真正玩三国杀肯定不像猪一样出牌啊 ...

  6. 串行&并行&并发,同步&异步

    1. 串行&并行&并发 1.1 串行 这个非常好理解,字面意思,像串成一个串一样,顺序执行 上一个没执行完的话,后面的就必须无条件等待 一般情况就是一个线程里:任务一个接一个执行,类似 ...

  7. 使用git将本地仓库上传到远程仓库(转)

    第一步:创建一个工程目录 执行: git init 第二步:把文件添加到版本库中,使用命令 git add .添加到暂存区里面去,不要忘记后面的小数点".",意为添加文件夹下的所有 ...

  8. 家庭记账本APP开发准备(二)

    今天学习了选项卡,为记账本的分类做了准备.主登录界面进行了优化,但仍未实现各个组件之间的跳转. 选项卡 activity_main.xml <?xml version="1.0&quo ...

  9. Docker 阿里镜像

    Docker 配置阿里镜像 Dokcer 拉取镜像非常慢,配置阿里镜像加速. 步骤 首先注册阿里云,找到 "容器镜像服务" --> "镜像加速器" ,复制 ...

  10. SpringCloud系列之API网关(Gateway)服务Zuul

    1.什么是API网关 API网关是所有请求的入口,承载了所有的流量,API Gateway是一个门户一样,也可以说是进入系统的唯一节点.这跟面向对象设计模式中的Facet模式很像.API Gatewa ...