在我们的应用程序中,我们使用Boost库(和ASIO进行网络通信)。
最近,我们发现如果我们通过同一个套接字从不同的线程发送数据,我们的客户端应用程序就会接收有害数据。
小测试以突出问题:
#include <stdio.h>
#include <boost/thread.hpp>
#include <boost/asio.hpp>
void send_routine(boost::shared_ptr<boost::asio::ip::tcp::socket> s, char c)
{
std::vector<char> data(15000, c);
data.push_back('\n');
for (int i=0; i<1000; i++)
boost::asio::write(*s, boost::asio::buffer(&data[0], data.size()));
}
int main()
{
using namespace boost::asio;
using namespace boost::asio::ip;
try {
io_service io_service;
io_service::work work(io_service);
const char* host = "localhost";
const char* service_name = "18000";
tcp::resolver resolver(io_service);
tcp::resolver::query query(tcp::v4(), host, service_name);
tcp::resolver::iterator iterator = resolver.resolve(query);
auto socket = boost::shared_ptr<tcp::socket>(new tcp::socket(io_service));
socket->connect(*iterator);
boost::thread t1(send_routine, socket, 'A');
boost::thread t2(send_routine, socket, 'B');
boost::thread t3(send_routine, socket, 'C');
t1.join();
t2.join();
t3.join();
}
catch (std::exception& e) {
printf("FAIL: %s\n", e.what());
}
return 0;
}
因此,我们在这里创建套接字,连接到localhost:18000
并启动3个将写入套接字的线程。
在不同的终端窗口中,我运行nc -l -p 18000 | tee out.txt | sort | uniq | wc -l
。我希望3
作为输出,但它在网络流中返回超过100个“不同的字符串”(因此,数据已损坏)。但它适用于较小的缓冲区大小(例如,如果我们将15000
更改为80
。
所以,问题是:它是ASIO库的正确行为吗?另一个:如何解决它?我应该在mutex
函数中使用send_routine
(还是有其他解决方案)?
答案 0 :(得分:5)
write
和async_write
以您使用它们的方式不是线程安全的。解决这个问题的规范方法是对邮件进行排队,然后一次一个地写出来。
答案 1 :(得分:2)
根据documentation tcp::socket
在多个线程之间共享时,它不是线程安全的
因此,您要么像boost::mutex
建议的那样进行同步,要么使用异步写入。 io_service
为你工作。
答案 2 :(得分:2)
是的还有另一种解决方案! Strands: Use Threads Without Explicit Locking。注意strands只为“事件处理程序”提供对socket的“原子”访问,当然你需要使用asio“事件处理程序”,而不是代码的情况。换句话说,你需要使用boost :: asio :: async_write而不是boost :: asio :: write。
答案 3 :(得分:0)
你可能有两个问题,例如,通过让一个专用于写入的线程和一个所有线程都在那里发布响应的队列来解决线程问题。您也可以将您的设计更改为异步设计并使用write_some()函数并让线程由io_service :: run()完成,它可以由多个线程运行。
其次,如果客户希望以相同的顺序回答问题,那么您可能会遇到协议问题。
HTH
的Torsten