Latelly我一直在使用多线程编码,经过一段时间的写作我意识到如果我在不同的boost :: threads中使用std :: cout,输出将没有逻辑顺序,我的程序测试类似于:
#include <boost/thread/thread.hpp>
#include <iostream>
int my01( void )
{
std::cout << "my01" << std::endl;
return 0;
}
/* my02, my03 and my04 are the same with different outputs*/
[...]
int main( void )
{
boost::thread t1(&my01);
boost::thread t2(&my02);
boost::thread t3(&my03);
boost::thread t4(&my04);
while(!t1.joinable() || !t2.joinable() || !t3.joinable() || !t4.joinable());
t1.join();
t2.join();
t3.join();
t4.join();
std::cout << "The end!" << std::endl;
getchar();
return 0;
}
输出通常就像(它改变):
my02my01
my04
my03
BLANK LINE
结束!
考虑到这个问题,我考虑创建一个单独的线程来管理所有输出,所以它们的顺序如下:
MY01
MY02
my03
my04
结束!
编写此类线程或管理这些输出的最佳方法是什么?
请同时阅读这个问题的答案:Is cout synchronized/thread-safe?
Ps:我使用的是Visual C ++ 2010 Express,我的cpu有8个不同的核心。
谢谢你的时间!
答案 0 :(得分:7)
首先,您可以考虑避免所有显式线程管理,而是使用std::async
在任意数量的单独线程中启动您的任务。
其次,您不想在线程本身中执行I / O,而是要创建结果,并按顺序执行输出。这意味着线程函数只是创建了一些数据,并将其留给调用者实际写出来:
// warning: untested code
std::string process(int value) {
std::ostringstream buffer;
buffer << "my" << std::setfill('0') << std::setw(2) << value;
return buffer.str();
}
然后我们需要以异步方式启动它的四个副本:
std::vector<std::future<std::string> > results;
for (int i=0; i<4; i++)
results.push_back(std::async(process, i));
然后我们等待结果并按顺序打印出来:
for (int i=0; i<4; i++)
std::cout << results[i].get();
我应该添加一个小问题:我基于标准C ++ 11 future
。我相信基本的想法也应该与Boost future
(标准所依据的)一起使用,但还没有对它进行测试。需要对Boost的未来进行一些微调(例如,名称) (尽管如上所述,代码未经过测试,可能需要进行一些调整标准期货也是如此,但无论如何都希望减少。)
答案 1 :(得分:4)
我通过编写一个瘦包装器来解决它,该包装器在开始写入流时锁定互斥锁并释放它,并在写入语句完成后刷新流。
用法:用safe_cout替换std :: cout。
请记住,它不支持花哨的std :: cout功能,如std :: endl。
请参阅下面的代码或从此处获取:https://github.com/dkorolev/felicity/blob/master/safe_ostream.h
#include <cassert>
#include <iostream>
#include <mutex>
#include <memory>
struct safe_ostream {
struct guarded_impl {
guarded_impl() = delete;
guarded_impl(const guarded_impl&) = delete;
void operator=(const guarded_impl&) = delete;
guarded_impl(std::ostream& ostream, std::mutex& mutex) : ostream_(ostream), guard_(mutex) {
}
~guarded_impl() {
ostream_.flush();
}
template<typename T> void write(const T& x) {
ostream_ << x;
}
std::ostream& ostream_;
std::lock_guard<std::mutex> guard_;
};
struct impl {
impl() = delete;
void operator=(const impl&) = delete;
impl(std::ostream& ostream, std::mutex& mutex) : unique_impl_(new guarded_impl(ostream, mutex)) {
}
impl(const impl& rhs) {
assert(rhs.unique_impl_.get());
unique_impl_.swap(rhs.unique_impl_);
}
template<typename T> impl& operator<<(const T& x) {
guarded_impl* p = unique_impl_.get();
assert(p);
p->write(x);
return *this;
}
mutable std::unique_ptr<guarded_impl> unique_impl_;
};
explicit safe_ostream(std::ostream& ostream) : ostream_(ostream) {
}
template<typename T> impl operator<<(const T& x) {
return impl(ostream_, mutex_) << x;
}
std::ostream& ostream_;
std::mutex mutex_;
};
safe_ostream safe_cout(std::cout);
safe_ostream safe_cerr(std::cerr);
答案 2 :(得分:2)
您需要在线程上强加一个订单,以便输出的顺序符合您的要求(可能通过将线程实例或事件传递给相应的线程,以便它们只能按您的顺序执行),或者你可以给所有输出一个线程序列号,将所有输出排队到一个'print'线程,并在那里保留一个任何无序行的列表,以便打印输出你想要的。
对于'真正的'应用程序,(即不是一个滥用线程的普通测试应用程序),线程在顺序缓冲区上并行执行大量工作,顺序缓冲区的顺序必须保留,强制线程等待彼此通常不是一个合理的选择。通常使用序列号并在之后重新组合缓冲流。
答案 3 :(得分:1)
给每个线程一个std::ostringstream
来写输出。在程序结束时,按顺序打印每个线程的输出。
考虑到线程4可能在线程1之前很久完成,你还会怎么做呢?
答案 4 :(得分:0)
使用锁定。如果你可以使用boost,那就行了。
int my01(boost::mutex *coutGuard)
{
{
// lock cout until the closing brace
boost::mutex::scoped_lock lock(*coutGuard);
std::cout << "my01" << std::endl;
}
return 0;
}
int main( void )
{
boost::mutex coutGuard;
boost::thread t1(boost::bind(&my01, &coutGuard));
...
}
可以使用lock_guard
代替scoped_lock
。