【发布时间】:2016-10-27 17:55:51
【问题描述】:
我们已经在生产环境中使用 asio 多年了,最近我们达到了一个临界点,我们的服务器负载刚好足以注意到一个神秘问题。
在我们的架构中,每个独立运行的独立实体都使用个人strand 对象。一些实体可以执行长时间的工作(从文件读取、执行 MySQL 请求等)。显然,这项工作是在用 strand 包裹的处理程序中执行的。一切听起来都很好,应该可以完美地工作,直到我们开始注意到一些不可能的事情,比如计时器在应该的几秒钟后过期,即使线程正在“等待工作”并且工作无缘无故地停止。看起来在一条链中执行的长时间工作对其他不相关的链产生了影响,不是全部,而是大多数。
花费了无数个小时来查明问题。轨迹导致strand对象的创建方式:strand_service::construct(here)。
出于某种原因,开发人员决定使用有限数量的strand 实现。这意味着一些完全不相关的对象将共享一个实现,因此会因此而成为瓶颈。
在独立(非增强)asio 库中使用了类似的方法。但不是共享实现,每个实现现在都是独立的,但可以与其他实现共享一个 mutex 对象 (here)。
这到底是怎么回事?我从未听说过系统中互斥体数量的限制。或与它们的创建/销毁相关的任何开销。虽然最后一个问题可以通过回收互斥而不是销毁它们来轻松解决。
我有一个最简单的测试用例来说明性能下降的严重程度:
#include <boost/asio.hpp>
#include <atomic>
#include <functional>
#include <iostream>
#include <thread>
std::atomic<bool> running{true};
std::atomic<int> counter{0};
struct Work
{
Work(boost::asio::io_service & io_service)
: _strand(io_service)
{ }
static void start_the_work(boost::asio::io_service & io_service)
{
std::shared_ptr<Work> _this(new Work(io_service));
_this->_strand.get_io_service().post(_this->_strand.wrap(std::bind(do_the_work, _this)));
}
static void do_the_work(std::shared_ptr<Work> _this)
{
counter.fetch_add(1, std::memory_order_relaxed);
if (running.load(std::memory_order_relaxed)) {
start_the_work(_this->_strand.get_io_service());
}
}
boost::asio::strand _strand;
};
struct BlockingWork
{
BlockingWork(boost::asio::io_service & io_service)
: _strand(io_service)
{ }
static void start_the_work(boost::asio::io_service & io_service)
{
std::shared_ptr<BlockingWork> _this(new BlockingWork(io_service));
_this->_strand.get_io_service().post(_this->_strand.wrap(std::bind(do_the_work, _this)));
}
static void do_the_work(std::shared_ptr<BlockingWork> _this)
{
sleep(5);
}
boost::asio::strand _strand;
};
int main(int argc, char ** argv)
{
boost::asio::io_service io_service;
std::unique_ptr<boost::asio::io_service::work> work{new boost::asio::io_service::work(io_service)};
for (std::size_t i = 0; i < 8; ++i) {
Work::start_the_work(io_service);
}
std::vector<std::thread> workers;
for (std::size_t i = 0; i < 8; ++i) {
workers.push_back(std::thread([&io_service] {
io_service.run();
}));
}
if (argc > 1) {
std::cout << "Spawning a blocking work" << std::endl;
workers.push_back(std::thread([&io_service] {
io_service.run();
}));
BlockingWork::start_the_work(io_service);
}
sleep(5);
running = false;
work.reset();
for (auto && worker : workers) {
worker.join();
}
std::cout << "Work performed:" << counter.load() << std::endl;
return 0;
}
使用以下命令构建它:
g++ -o asio_strand_test_case -pthread -I/usr/include -std=c++11 asio_strand_test_case.cpp -lboost_system
以通常的方式进行测试:
time ./asio_strand_test_case
Work performed:6905372
real 0m5.027s
user 0m24.688s
sys 0m12.796s
长时间阻塞工作的测试运行:
time ./asio_strand_test_case 1
Spawning a blocking work
Work performed:770
real 0m5.031s
user 0m0.044s
sys 0m0.004s
差异很大。发生的情况是每个新的非阻塞工作都会创建一个新的strand 对象,直到它与阻塞工作的strand 共享相同的实现。当这种情况发生时,它是一个死胡同,直到漫长的工作完成。
编辑:
将并行工作减少到工作线程的数量(从1000 到8)并更新了测试运行输出。这样做是因为当两个数字接近时,问题更加明显。
【问题讨论】:
标签: c++ boost-asio