【问题标题】:std::thread vs pthead, what am I doing wrong?std::thread vs pthead,我做错了什么?
【发布时间】:2019-11-28 01:28:48
【问题描述】:

我正在做一个需要在 docker 容器内执行一些进程的项目。我想处理进程没有按时终止(比如说 10 秒内)的情况。

我正在使用这个DockerClientpp 库来管理容器,这些容器基本上只是向 Docker 套接字发出 HTTP 请求。到目前为止一切都很好。

为了停止一个耗时过长的容器,我使用了一个单独的线程。问题是我能够使用 ptheads 来实现它,但我找不到使用 std::threadlambas

的方法

这是我使用 pthread 的工作实现

void *ContainerManager::spawnKiller(void *ref) {
    ContainerManager *self = (ContainerManager *)ref;
    std::unique_ptr<DockerClientpp::DockerClient> dc(new DockerClientpp::DockerClient());

    std::cout << "[slave]forceStop(): Waiting " << self->timeOut << " before stopping " << self->activeId << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(self->timeOut));
    try {
        dc->stopContainer(self->activeId);
        std::cout << "[slave]forceStop(): Container will be force-stopped" << std::endl;
    } catch(std::exception &e) {
        // container has already been destroyed
        std::cout << "[slave]forceStop(): Error => " << e.what() << std::endl;
    }
    pthread_exit(0);
}

void ContainerManager::execute() {
    pthread_t killerId;
    pthread_create(&killerId, nullptr, &(ContainerManager::spawnKiller), (void *)this);
    pthread_detach(killerId);
}

这是我的 std::thread 和 lambda 实现,一旦我尝试分离线程,SEGFAULT 就会失败。

void ContainerManager::execute() {
    std::thread([this]() {
        std::this_thread::sleep_for(std::chrono::seconds(timeOut));
        try {
            dc->stopContainer(activeId);
            std::cout << "[slave]forceStop(): Container will be force-stopped" << std::endl;
        } catch(std::exception &e) {
            // container has already been destroyed
            std::cout << "[slave]forceStop(): Error => " << e.what() << std::endl;
        }
    }).detach();
}

这就是 gdb 显示的内容

Thread 1 "test" received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x00000000007c6801 in std::thread::detach() ()
#2  0x0000000000410785 in ContainerManager::execute (this=0x7fffffffe2a0, processName=...)
    at ../container_manager.cpp:223
#3  0x0000000000412c99 in ContainerManager::executeNew (this=0x7fffffffe2a0, processName=..., 
    replace=false, language=@0x7fffffffe020: ContainerManager::GO) at ../container_manager.cpp:336
#4  0x00000000004094a9 in main () at test.cpp:36

我尝试使用常规函数而不是lamba,我尝试捕获参数,我还尝试将参数作为参数传递,但我被卡住了。

我没有尝试使用new thread(...) 动态分配线程,但据我了解,即使std::thread 变量超出范围,线程仍然存在。

你对我做错了什么有什么建议吗?我觉得我真的错过了关于 std::thread 和 lambda 的一些东西。

execute 方法是 ContainerManager 类的一个方法,它保证在生成的线程终止之前不会超出范围,也是我使用的变量(timeOutactiveId 是对象的字段)


编辑: 看来detach()确实有问题

如果我运行这个

void ContainerManager::execute() {
    int *t = new int;
    *t = timeOut;
    std::string *s = new std::string;
    *s = activeId;
    std::thread x([&t, &s]() {
        std::cout << "LOL" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(*t));
        std::unique_ptr<DockerClientpp::DockerClient> _dc(new DockerClientpp::DockerClient());
        try {
            _dc->stopContainer(*s);
            std::cout << "[slave]forceStop(): Container will be force-stopped" << std::endl;
        } catch(std::exception &e) {
            // container has already been destroyed
            std::cout << "[slave]forceStop(): Error => " << e.what() << std::endl;
        }
    });
    std::cout << "Detaching" << std::endl;
    if(x.joinable()) {
        std::cout << ".. in a moment" << std::endl;                                                                             
        x.detach();
    }
}

我得到这个输出

Detaching
.. in a moment
Segmentation fault (core dumped)

编辑 2 我尝试在笔记本电脑上运行此代码,一切正常

void ContainerManager::execute() {
    // activeId and timeOut are fields of the ContainerManager object
    std::thread([this]() {
        std::this_thread::sleep_for(std::chrono::seconds(timeOut));
        std::unique_ptr<DockerClientpp::DockerClient> dc(new DockerClientpp::DockerClient());
        try {
            dc->stopContainer(activeId);
            std::cout << "[slave]forceStop(): Container will be force-stopped" << std::endl;
        } catch(std::exception &e) {
            // container has already been destroyed
            std::cout << "[slave]forceStop(): Error => " << e.what() << std::endl;
        }
    }).detach();
}

【问题讨论】:

  • 我没有看到 dc 在 lambda 版本中被分配,而在 pthreads 版本中,它是由线程专门分配的。
  • 多线程程序经常在一台机器上显示错误,而在另一台机器上却没有。有时只需要执行速度和线程同步的微小差异。
  • 您说您在使用@MikevanDyke 的代码时遇到了同样的问题——您能否更新您的问题以显示您正在运行的实际代码?迈克修复了一个真正的错误,但如果有其他问题,看看你到底有什么会很有帮助。
  • 你是如何在容器中启动进程的?如果你正在做docker run -d ubuntu:14.04 /my/process,用docker run -d ubuntu:14.04 timeout 10s /my/process 替换它不会有用吗?它甚至返回错误代码 124 来告诉您进程超时。
  • 根据您的设计,您需要考虑使用 docker 引擎处理所有内容是否值得添加一个线程、以下解决方案中约 20 行非平凡的 c++ 代码,以及您处理的任何错误从 stopContainer()DockerClientpp::DockerClient 构造函数中丢失。

标签: c++ multithreading lambda pthreads stdthread


【解决方案1】:

在线程中,您正在访问对变量 int *tstd::string *s 的引用,它们是 ContainerManager::execute() 方法的本地变量。 ContainerManager::execute() 完成后,对这两个变量的访问会导致未定义的行为,在您的情况下会导致 SEGFAULT。而是将每个值的两个指针传递给 lamdba(甚至更好:根本不要使用 new):

void ContainerManager::execute() {
    int *t = new int;
    *t = timeOut;
    std::string *s = new std::string;
    *s = activeId;
    std::thread x([t, s]() { // <<--- Pass by value
        std::cout << "LOL" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(*t));
        std::unique_ptr<DockerClientpp::DockerClient> _dc(new DockerClientpp::DockerClient());
        try {
            _dc->stopContainer(*s);
            std::cout << "[slave]forceStop(): Container will be force-stopped" << std::endl;
        } catch(std::exception &e) {
            // container has already been destroyed
            std::cout << "[slave]forceStop(): Error => " << e.what() << std::endl;
        }
    });
    std::cout << "Detaching" << std::endl;
    if(x.joinable()) {
        std::cout << ".. in a moment" << std::endl;                                                                             
        x.detach();
    }
}

【讨论】:

  • 确实如此,我没有意识到这一点。但这不是问题,当我还在方法中时,就在调用“detach()”之后,我得到了段错误。我尝试按值捕获指针,问题是一样的
  • @fedemengo 当你 join 而不是 detach 时,它还会发生吗?您是否尝试过其他编译器?
  • 好吧,您的问题似乎仍然只显示根据此答案客观破坏的代码。也许您可以在演示相同问题的固定代码中进行编辑?
  • @Useless 你的评论地址是fedemengo,不是吗?
  • 确实如此。我会评论这个问题,因为 @ 在评论线程中不起作用......
【解决方案2】:

对我来说,段错误表明该类超出范围,即使您希望它不会。另一种可能性是您正在访问的变量存在竞争条件。

与其在 lambda 中捕获 this,不如尝试通过复制将 all 变量传递给 lambda。这将消除与范围有关的任何竞争条件,并解决任何潜在的生命周期问题,因为 lambda 将与任何其他线程完全分离。当然,这意味着没有指向其他地方的数据的指针或引用,请确保您确实在做 timeOutactiveId 的完整副本。

或者,我建议将线程存储为类的数据成员,而不是detach。然后,join 在析构函数中。如果线程提前结束,join 基本上是空操作。如果线程没有完成,这将防止线程正在使用的资源超出范围,直到线程完成。这将解决超出范围的变量,但不会解决任何竞争条件。竞争条件可以通过使用std::atomic 或互斥锁来解决。

由于第二种解决方案(使用joinstd::atomic 和/或互斥锁)更加复杂,需要检查生命周期和竞争条件,我建议使用第一种解决方案(使用不捕获任何内容的 lambda,如果可能,所有参数都通过副本传递)。

【讨论】:

  • 我尝试只复制变量,但问题仍然存在,仅当我尝试 detach 线程时才会出现问题。我打算先存储线程,然后将其加入构造函数中,我真的很喜欢这个解决方案
  • 如果你有时间信息,你可以看看问题是真的分离还是线程。如果您正在休眠 10 秒,并且所有内容都通过副本传递,我希望您可以看到崩溃将立即发生(并且分离导致崩溃)或稍后(并且线程在休眠后导致崩溃)。另一种选择是您根本没有睡觉(t = 0),并且由于 stopContainer 中的某些内容(可能是由于违反其合同或错误的库代码)而发生崩溃。您还可以删除所有线程的代码以使其更简单。很多选择。
  • 我只是有点怀疑。 Segfault 的原因通常与无效访问内存有关。分离本身与此无关。我非常不愿意将手指指向分离。这更有可能是分离的结果。我会先尝试将线程变为无操作,看看是否仍然存在段错误,然后添加代码行。在您的第一个示例中,您从未显示控制台输出,因此我无法判断分离本身就是问题所在。并且打印语句也不是决定性的,如果是段错误或刷新的竞赛,则段错误可能总是第一个。
  • 另外,如果代码是用任何优化编译的,我也不相信 gdb。那时任何事情都可能发生。通常,并行代码在优化和不优化的情况下会表现不同,因为这会改变时序,从而改变序列,进而改变行为。这就是让调试并行代码变得困难的原因,对我来说,似乎在调试中工作的东西在发布时可能会失败。然后我不能相信发布时的调试器,所以我不得不回退到其他神秘的方法。
  • 我尝试捕获单个变量,现在正在工作。我 100% 确定我已经尝试过了,但显然我错过了一些东西。我猜detach 不是问题
猜你喜欢
  • 2020-12-21
  • 2011-06-01
  • 1970-01-01
  • 2019-07-04
  • 1970-01-01
  • 2021-04-14
  • 2021-04-19
  • 2017-03-13
  • 2017-01-18
相关资源
最近更新 更多