【问题标题】:multithreading overhead with mutually independent threads [closed]具有相互独立线程的多线程开销[关闭]
【发布时间】:2016-08-20 08:40:58
【问题描述】:

我目前正在学习 c++11 中引入的多线程特性,当我筛选几个 SO 问题时,我发现多线程在执行指令的同时会带来自己的开销。所以我写了一个简单的程序来比较黑白顺序和多线程解决方案。结论并没有让我感到惊讶,顺序比多线程快得多,可能是因为它不必处理线程的创建和管理。

但是现在对于另一个问题,顺序方法会阻塞整个程序,多线程可能会占优势 -

input.txt

DEVICE#1 4
DEVICE#2 5
DEVICE#3 10
DEVICE#4 1
DEVICE#1 1

prog_seq.cpp

#include <chrono>
#include <fstream>
#include <iostream>
#include <string>
#include <thread>

using string = std::string;
using namespace std::this_thread;  // sleep_for

void executor(string name, int delay)
{
    sleep_for(std::chrono::seconds(delay));
    std::cout << name << std::endl;
}

int main()
{
    string deviceName = "";
    int delay         = 0;

    std::ifstream in("input.txt");

    while (in >> deviceName >> delay) {
        executor(deviceName, delay);
    }

    return 0;
}

Output

DEVICE#1
DEVICE#2
DEVICE#3
DEVICE#4
DEVICE#1

real    0m21.004s
user    0m0.000s
sys 0m0.000s

这个程序至少需要 21 秒才能完成,而下面的程序可能会在 11 秒内完成-

prog_thread.cpp

#include <chrono>
#include <fstream>
#include <iostream>
#include <string>
#include <thread>
#include <vector>

using string = std::string;
using namespace std::this_thread;  // sleep_for

void executor(string name, int delay)
{
    sleep_for(std::chrono::seconds(delay));
    std::cout << name << std::endl;
}

int main()
{
    std::vector<std::thread> threadStore;
    string deviceName = "";
    int delay         = 0;

    std::ifstream in("input.txt");

    while (in >> deviceName >> delay) {
        threadStore.emplace_back(std::thread(executor, deviceName, delay));
    }

    for (auto &t : threadStore) {
        t.join();
    }
    return 0;
}

Output

DEVICE#1
DEVICE#4
DEVICE#1
DEVICE#2
DEVICE#3

real    0m10.003s
user    0m0.000s
sys 0m0.000s

所以我知道,对于此类程序,多线程实际上会受益,因为它们会以最短的顺序完成,因为它们的切换方式很好(如果它们的数量增长高于核心)。

现在我想问的是,对于这样的程序,每个线程都在执行相互独立的任务,这些任务并不完全是 cpu 密集型,而是更多地依赖于等待来自外部系统的输入或响应/人类,多线程的开销在这里仍然适用吗?假设我有 50 个线程都在等待来自 50 个客户端的输入,并且由于它们忙于执行 cpu 密集型任务,与顺序执行中的某种计时器相比,这将是一个更好的解决方案(它保持300 毫秒后一次又一次地检查输入)。

此外,是否有更多替代方法来处理此类问题,因为我很想了解它们。

【问题讨论】:

  • “假设我有 50 个线程都在等待来自 50 个客户端的输入” 这是许多 Web 服务器程序的情况,它们通常有一个线程池,其中一个线程使用每个单独的连接。除此之外,我担心你的问题太宽泛,无法在这里简明扼要地回答。因素太多了。
  • @πάνταῥεῖ by threadpool 您指的是自行创建的固定数量的线程吗?还是由操作系统管理的某种线程池?
  • 通常线程池是在程序中自己创建的。
  • 我想了解这些因素本身。有什么资源可以阅读吗?除了我做过的幼稚示例之外,任何可以衡量这些开销的工具。我的知识限制了我直到这里,这就是我在 SO 上问这个问题的原因。
  • 不幸的是,在这里要求第三方资源或工具是题外话。

标签: c++ linux multithreading performance c++11


【解决方案1】:

正如 cmets 中所述,线程管理是 Comp Sci 中一个非常广泛且经过深入研究的主题。对于您所在的情况,我可以举几个行业示例,但您可能会发现它们具有说明性。

一般来说,从程序员的角度来看,拥有多个受 I/O 限制的线程是一种简单且可维护的解决方案。每个套接字连接启动一个线程是一个常见的例子。只要线程随着时间的推移进行大量工作,线程设置/拆除就无关紧要。通常,Linux 调度程序会很好地为所有线程提供服务。当线程可以等待诸如套接字读取之类的阻塞资源时,此模型特别好。如果所有线程都非常繁忙,那么模型就不好了,上下文切换所花费的时间将是巨大的。根据延迟窗口,一些线程也可能超时,因为抢占很常见。

另一方面,Reactor 模型可以与一小组线程一起使用。在此模型中,每个 cpu 核心启动一个线程。线程“热”运行,不允许阻塞。许多客户端可以在单个 Reactor 中以循环或类似方式提供服务。可以启动多个独立的反应器来扩展系统。如果所有客户端都忙于几乎没有 I/O,则此模型很好。 Reactor 模型最适合延迟敏感的应用程序。 Reactor 将接受一个特定的作业,并在没有抢占的情况下运行完成。

Node.js 引擎使用与 Reactor 类似的模型,称为Observer。这两种模式相似但不完全是same

Reactor/Observer 模型的缺点是作业处理的异步性质。许多业务应用程序都有一个自然的阻塞或循环点。必须将算法重构为异步通常会令人困惑并影响可维护性。对 Node 文献的简要调查将揭示对异步算法的痴迷。线程模式是一个关键原因。

线程管理与排队论密切相关。业务应用程序的最佳线程模型通常是传入作业的排队模型的表达。 Queuing Theory 是一个复杂而有趣的领域。

总之,对于大规模、高吞吐量和/或低延迟的应用程序,线程模型是一个关键的架构决策。要获得成功的解决方案,需要对业务问题和基础计算机科学有透彻的了解。在我的blog 上查看有关此主题的更多信息。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多