我有一个程序,它使用boost :: asio将工作分配给多个线程。这项工作包括启动一个http客户端,发出请求并将答案存储在一个文件中。有时会出现一个错误导致程序永远不会完成并停止写入任何输出。我一直无法弄清楚到底出了什么问题,因为该程序没有报告任何可以解释这种行为的错误或问题(它会报告偶尔的超时或其他一些小问题)。
我在Windows上并在控制台中输入“netstat -n”表示该程序在停止工作后很长时间内与目标主机建立了8个已建立的连接(每个线程有一个连接)。
使用的互斥锁:
std::mutex catch_mx, result_mx, debug_mx;
将工作分配给线程:
boost::asio::io_service io_service;
for (auto &wordset : wordsets)
for (auto &unicode_string : wordset.variants)
io_service.post(std::bind(send_query, std::ref(io_service), std::ref(unicode_string)));
std::vector<std::thread> threads;
threads.reserve(std::max(1u, std::thread::hardware_concurrency()));
for (auto i = 0u; i < threads.capacity(); ++i)
threads.emplace_back(thread_function, std::ref(io_service));
for (auto &t : threads)
t.join();
允许线程接收工作:
void thread_function(boost::asio::io_service &io_service)
{
io_service.run();
}
发出http请求并解释响应的函数。 http客户端代码已从boost :: asio同步http客户端示例中复制。唯一的区别在于错误处理和写入文件而不是std::cout
void send_query(boost::asio::io_service &io_service, const Ustring &unicode_string)
{
try
{
using boost::asio::ip::tcp;
auto query_string = generate_query(unicode_string);
debug_log(unicode_string, query_string);
tcp::resolver resolver(io_service);
tcp::resolver::query query("somehost.com", "http");
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::socket socket(io_service);
boost::asio::connect(socket, endpoint_iterator);
boost::asio::streambuf request;
std::ostream request_stream(&request);
request_stream << "GET " << "somepath" + query_string << " HTTP/1.0\r\n";
request_stream << "Host: " << "somehost.com" << "\r\n";
request_stream << "Accept: */*\r\n";
request_stream << "Connection: close\r\n\r\n";
boost::asio::write(socket, request);
boost::asio::streambuf response;
boost::asio::read_until(socket, response, "\r\n");
std::istream response_stream(&response);
std::string http_version;
response_stream >> http_version;
unsigned int status_code;
response_stream >> status_code;
std::string status_message;
std::getline(response_stream, status_message);
if (!response_stream || http_version.substr(0, 5) != "HTTP/")
throw AUTO_EXCEPTION("invalid response");
if (status_code != 200) // for now, consider this an error
throw AUTO_EXCEPTION("response status code " + std::to_string(status_code));
boost::asio::read_until(socket, response, "\r\n\r\n");
std::stringstream ss;
std::string header;
while (std::getline(response_stream, header) && header != "\r");
ss << header << "\n";
ss << "\n";
if (response.size() > 0)
ss << &response;
boost::system::error_code error;
while (boost::asio::read(socket, response, boost::asio::transfer_at_least(1), error))
ss << &response;
if (error != boost::asio::error::eof)
throw AUTO_EXCEPTION(error.message());
write_result(ss.str());
}
catch (const std::exception &e)
{
std::unique_lock<std::mutex> lock(catch_mx);
std::ofstream ofs("error.log", std::ios_base::app);
ofs << "Thread " << std::this_thread::get_id() << ": " << e.what() << std::endl;
ofs.close();
}
}
记录功能
void debug_log(const Ustring &code_points, std::string &query)
{
std::unique_lock<std::mutex> lock(debug_mx);
std::ofstream ofs("debug.log", std::ios_base::app);
ofs << unicode_to_string(code_points) << " " << query << std::endl;
ofs.close();
}
void write_result(const std::string &s)
{
std::unique_lock<std::mutex> lock(result_mx);
std::ofstream ofs("results.txt", std::ios_base::app);
ofs << s << std::endl;
ofs.close();
}
PS:就像AndyT建议的那样,我发现线程似乎都陷入了boost :: asio函数中的同一步(在socket_ops.ipp中):
signed_size_type recv(socket_type s, buf* bufs, size_t count,
int flags, boost::system::error_code& ec)
{
clear_last_error();
#if defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
// Receive some data.
DWORD recv_buf_count = static_cast<DWORD>(count);
DWORD bytes_transferred = 0;
DWORD recv_flags = flags;
int result = error_wrapper(::WSARecv(s, bufs,
recv_buf_count, &bytes_transferred, &recv_flags, 0, 0), ec); // this is where they all get stuck
if (ec.value() == ERROR_NETNAME_DELETED)
ec = boost::asio::error::connection_reset;
else if (ec.value() == ERROR_PORT_UNREACHABLE)
ec = boost::asio::error::connection_refused;
if (result != 0)
return socket_error_retval;
ec = boost::system::error_code();
return bytes_transferred;
#else // defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
msghdr msg = msghdr();
msg.msg_iov = bufs;
msg.msg_iovlen = static_cast<int>(count);
signed_size_type result = error_wrapper(::recvmsg(s, &msg, flags), ec);
if (result >= 0)
ec = boost::system::error_code();
return result;
#endif // defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
}
答案 0 :(得分:0)
这看起来像死锁。使用锁的异步代码是常见问题。尝试在boost :: asio代码中使用strands而不是lock。您可以将处理程序发布到您的io_service并使用不同的链包装它们。一行用于调试,一行用于写输出,一行用于处理错误。例如,当你需要编写调试信息时 - 你需要创建执行它的处理程序,而不是用相应的strand包装它,然后将它发布到io_service。
最好对所有I / O使用异步操作。