我正在比较线程池的不同实现。一种包含锁和条件变量的方法比另一种使用无锁实现的方法慢得多(约 100 微秒)。
当然,我可以合理解释为什么无锁实现比带锁和条件变量的实现更快,但我想深入问题的核心并获取比较这些方法的数据(除了总运行时间)。
较慢实现的一个片段是:
void ThreadMain(std::stop_token stoken, ThreadPool* pool,
std::atomic_flag& start,
std::vector<ThreadPool::timestamp>& stamps) {
while (!stoken.stop_requested()) {
std::unique_lock<std::mutex> lock(pool->mutex);
pool->mutex_condition.wait(lock, [&] { return start.test(); });
stamps.push_back(high_resolution_clock::now());
if (stoken.stop_requested()) {
return;
}
int i{0};
while ((i = pool->idx.fetch_sub(1) - 1) > -1) {
(*(pool->task))(pool->ctx, i);
}
start.clear();
if ((pool->completed_tasks.fetch_add(1) + 1) == pool->threads.size()) {
pool->completed.notify_one();
}
}
}
void ThreadPool::QueueTask(ThreadPool::function* function, void* context,
int r) {
{
std::unique_lock<std::mutex> lock(mutex);
task = function;
ctx = context;
completed_tasks = 0;
idx.store(r, order); // ensure that every thread know
for (std::atomic_flag& start : starts) {
start.test_and_set();
}
}
mutex_condition.notify_all();
timestamps[0].push_back(high_resolution_clock::now());
int i{0};
while ((i = idx.fetch_sub(1, order) - 1) > -1) {
(*(function))(context, i);
}
{
std::unique_lock<std::mutex> lock(mutex);
completed.wait(
lock, [this] { return completed_tasks.load() == threads.size(); });
for (std::atomic_flag& flag : starts) {
flag.clear();
}
completed_tasks = 0;
}
}
主线程在函数
ThreadPool::QueueTask
中接受新的工作负载,设置该函数,并复制该函数的上下文。然后主线程为每个线程设置条件变量,最后唤醒它们。完成所有这些后,我用 timestamps[0].push_back(high_resolution_clock::now());
获取当前时间。工作线程循环通过 void ThreadMain(std::stop_token stoken, ThreadPool* pool, std::atomic_flag& start, std::vector<ThreadPool::timestamp>& stamps)
。当工作线程通过时需要从睡眠中唤醒 pool->mutex_condition.wait(lock, [&] { return start.test(); });
。我用 stamps.push_back(high_resolution_clock::now());
定义了工作线程再次变得有用的时间。主线程唤醒其他工作人员和他们开始工作之间的持续时间约为 100 微秒。这 100 微秒相当于线程池的快实现和慢实现之间的运行时差异。
我想知道这100微秒到底发生了什么。我可以考虑使用 perf 或跟踪。我推测,查看主线程调用
notify_all
和传递 pool->mutex_condition.wait(lock, [&] { return start.test(); });
的工作线程之间的堆栈跟踪会提供更多信息。这只是一个猜测,我不确定如何实现。
应用程序的相关部分运行大约一毫秒。我使用 Linux 测试系统。我可以做什么来记录为什么一种方法比另一种方法慢?
某种具有精确采样的分析器会显示工作线程何时实际工作。有些甚至可以查明应用程序花费太多时间等待的地方。
从代码中我已经看到了“差距”的一些潜在原因:
high_resolution_clock()
并不是测量亚秒级持续时间的非常精确的方法。它可以不是单调的(即时间可以倒退),具体取决于实现。此外,底层时钟没有任何特定的分辨率保证。 (参见“注释”部分:https://en.cppreference.com/w/cpp/chrono/high_resolution_clock)