【问题标题】:What is the maximum number of threads that std::async will create and execute asynchronously?std::async 将异步创建和执行的最大线程数是多少?
【发布时间】:2017-03-01 02:23:11
【问题描述】:

我有大量 (>>100K) 任务,具有非常高的延迟(分钟)和非常少的资源消耗。可能它们都可以并行执行,我正在考虑使用std::async 为每个任务生成一个未来。

我的问题是: std::async 将异步创建和执行的最大线程数是多少? (在 Ubuntu 16-xx 或 CentOs 7.x - x86_64 上使用 g++ 6.x)

正确计算这个数字对我来说很重要,因为如果我没有足够的任务实际并行运行(等待),延迟的累积成本将会非常高。

为了得到答案,我首先检查了系统的功能:

bob@vb:~/programming/cxx/async$ ulimit -u
43735
bob@vb:~/programming/cxx/async$ cat /proc/sys/kernel/threads-max 
87470

根据这些数字,我预计能够以 43K 线程的顺序并行运行(主要是等待)。为了验证这一点,我编写了下面的程序来检查不同线程 ID 的数量以及使用空任务调用 100K std::async 所需的时间:

#include <thread>
#include <future>
#include <iostream>
#include <vector>
#include <algorithm>
#include <chrono>
#include <string>

std::thread::id foo()
{
    using namespace std::chrono_literals;
    //std::this_thread::sleep_for(2s);
    return std::this_thread::get_id();
}

int main(int argc, char **argv)
{
    if (2 != argc) exit(1);
    const size_t COUNT = std::stoi(argv[1]);
    std::vector<decltype(std::async(foo))> futures;
    futures.reserve(COUNT);
    while (futures.capacity() != futures.size())
    { 
        futures.push_back(std::async(foo));
    } 
    std::vector<std::thread::id> ids;
    ids.reserve(futures.size());
    for (auto &f: futures)
    { 
        ids.push_back(f.get());
    } 
    std::sort(ids.begin(), ids.end());
    const auto end = std::unique(ids.begin(), ids.end());
    ids.erase(end, ids.end());
    std:: cerr << "COUNT: " << COUNT << ": ids.size(): " << ids.size() << std::endl;
}

时间还不错,但不同线程 ID 的数量比预期的要少得多(32748 而不是 43735):

bob@vb:~/programming/cxx/async$ /usr/bin/time -f "%E" ./testAsync 100000
COUNT: 100000: ids.size(): 32748
0:03.29

然后我取消注释foo 中的睡眠线以添加 2 秒的睡眠时间。生成的时间与 2s 到 10K 左右的任务一致,但在某些时候,一些任务最终共享相同的线程 id,并且每增加一个任务,经过的时间增加 2s:

bob@vb:~/programming/cxx/async$ /usr/bin/time -f "%E" ./testAsync 10056
COUNT: 10056: ids.size(): 10056
0:02.24
bob@vb:~/programming/cxx/async$ /usr/bin/time -f "%E" ./testAsync 10057
COUNT: 10057: ids.size(): 10057
0:04.27
bob@vb:~/programming/cxx/async$ /usr/bin/time -f "%E" ./testAsync 10058
COUNT: 10058: ids.size(): 10057
0:06.28
bob@vb:~/programming/cxx/async$ ps -eT | wc -l
277

所以,对于我的问题,在这个系统上,限制大约是 10K。我检查了另一个系统,限制是 4K 的顺序。

我想不通:

  • 为什么这些值这么小
  • 如何根据系统规格预测这些值

【问题讨论】:

  • 嗯,这些线程中的每一个都需要从操作系统获取一些资源。线程堆栈的典型默认大小为 8MB,因此总共需要 thread-count*8MB 的 DRAM。这不仅仅是要启动更多线程,您还需要拥有资源……也请阅读here
  • 如果没有几乎同样荒谬的处理核心数量,荒谬的任务数量并不是那么有用。线程将花费大部分时间来争夺对处理器的访问权。考虑使用线程池。
  • @arash 感谢您的链接。关于 DRAM 的使用,即使默认线程堆栈大小为 8MB,我也希望这是虚拟内存,并且实际所需的 DRAM 数量将是线程实际使用的(四舍五入到页面大小)。我错了吗?
  • @user4581301 我可以在并行并发进程中运行超过 10K 的这些任务。我希望使用 std::async 我至少具有相同的能力。线程池有什么帮助?
  • @Come Raczy,嗯,8MB 是最大值,实际上你需要的更少(实际数量取决于堆栈外分配的数量),而且它确实是虚拟内存。但请记住,在实际执行某些操作的程序(而不是这段代码)中,推动您开始依赖交换的线程数。从性能的角度来看,大量线程意味着操作系统方面在调度、更多交换、竞争线程方面的工作更多——>开销。高性能应用程序有一个固定的线程池,等于硬件线程的数量,并将工作推送到队列中,线程从中获取工作。

标签: c++ linux multithreading asynchronous g++


【解决方案1】:

在 linux 上使用 g++,直接的答案似乎是“在 pthread_create 失败并返回 EAGAIN 之前可以创建的最大线程数”。这个数字可以被几个不同的值限制,man pthread_create 列出了其中的 3 个:

  • RLIMIT_NPROC:软资源限制(在我的 CentOs 7 服务器上为 4096,在我的 Ubuntu/VirtualBox 笔记本电脑上为 43735)
  • /proc/sys/kernel/threads-max 的值(分别为 2061857 和 87470)
  • /proc/sys/kernel/pid_max 的值(分别为 40960 和 32768)

systemd 至少还有一个其他可能的限制,正如man logind.conf 所指出的那样:

UserTasksMax= 设置每个用户可以同时运行的操作系统任务的最大数量。这控制了每个用户切片单元的 TasksMax= 设置,有关详细信息,请参阅 systemd.resource-control(5)。默认为 33%,这等于 10813 与主机上内核的默认值,但在 OS 容器中可能更小。

【讨论】:

    猜你喜欢
    • 2020-08-08
    • 1970-01-01
    • 2016-12-17
    • 2013-02-24
    • 1970-01-01
    • 1970-01-01
    • 2023-02-04
    • 1970-01-01
    • 2013-01-27
    相关资源
    最近更新 更多