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. MacOS SVN简单入门

    背景:MacOS内置了SVN的客户端和服务器端的软件,下边所使用到的目录需要结合自己电脑的具体情况进行设置,并不是很困难. MacOS SVN简单入门 第一部分,创建本地的SVN测试仓库,并修改相应的 ...

  2. MacOS安装npm

    原文链接:https://www.cnblogs.com/zmdComeOn/p/11543441.html 第一步:检查brew -v是否安装了homebrew这个MacOS 缺失的软件包的管理器. ...

  3. 十进制转换为其他进制(不使用format)

    base = [str(x) for x in range(10)] + [chr(x) for x in range(ord('A'), ord('A') + 6)] # ['0', '1', '2 ...

  4. numpy第三方库

    # 导入numpy 并赋予别名 np import numpy as np # 创建数组的常用的几种方式(列表,元组,range,arange,linspace(创建的是等差数组),zeros(全为 ...

  5. PHP gregoriantojd() 函数

    ------------恢复内容开始------------ 实例 把格利高里历法的日期转换为儒略日计数,然后再转换回格利高里历法的日期: <?php$jd=gregoriantojd(6,20 ...

  6. BSOJ 5445 -- 【2018雅礼】树 prufer序列 dp

    BSOJ在哪我也不知道 没有链接. 对于有标号无根树的统计和有度数限制 一般采用prufer序列. 根据prufer序列 容易知道 某个点的出现次数+1为当前点的度数. 对于这道题 考虑设f[i][j ...

  7. markdown利器-小书匠

    markdown利器-小书匠 为什么要用markdown语法编写文档? 编写文档的好处这里就不多说了.相信很多人都会在一些博客网站上发布自己的博客,那么怎么能使得自己的博客内容更加具有通用性呢?正如j ...

  8. 解决 IntelliJ IDEA占用C盘过大空间问题

    原文地址:https://blog.csdn.net/weixin_44449518/article/details/103334235 问题描述: 在保证其他软件缓存不影响C盘可用空间的基础上,当我 ...

  9. C++虚函数相关内容

    样例代码 class Base{public: Base(){}; virtual ~Base(){    //若没有设置为虚函数:如果有这样的指针Base *p=new Derived();声明,则 ...

  10. 配置JDK的环境变量

    1.官网下载JDK安装包并进行安装,记住安装目录 2.安装完JDK后配置环境变量  计算机→属性→高级系统设置→高级→环境变量 3.系统变量→新建 JAVA_HOME 变量 .变量值填写jdk的安装目 ...