我如何理解为什么生成的线程需要那么长时间才能唤醒?

问题描述 投票:0回答:1

我正在比较线程池的不同实现。一种包含锁和条件变量的方法比另一种使用无锁实现的方法慢得多(约 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 测试系统。我可以做什么来记录为什么一种方法比另一种方法慢?

c++ linux multithreading trace perf
1个回答
0
投票

某种具有精确采样的分析器会显示工作线程何时实际工作。有些甚至可以查明应用程序花费太多时间等待的地方。

从代码中我已经看到了“差距”的一些潜在原因:

  • high_resolution_clock()
    并不是测量亚秒级持续时间的非常精确的方法。它可以不是单调的(即时间可以倒退),具体取决于实现。此外,底层时钟没有任何特定的分辨率保证。 (参见“注释”部分:https://en.cppreference.com/w/cpp/chrono/high_resolution_clock
  • 睡眠/等待/互斥可以使用系统调用(会引入延迟),并且内核在调度线程时存在一些延迟。例如,对于 Windows AFAIK,最小“睡眠”时间可能高达 15 毫秒,即使您请求 1 毫秒
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.