std::thread和pthread,我做错了什么?

4

我正在开发一个项目,需要在Docker容器内执行一些进程。我希望处理在规定时间(比如10秒内)内未能终止的进程。

为管理容器,我使用DockerClientpp库,它基本上只是向Docker套接字发出HTTP请求,到这里都很顺利。

为了停止长时间运行的容器,我使用了一个单独的线程。问题是,我可以使用pthreads实现它,但我无法找到使用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

我尝试使用普通函数而非lambda,我尝试捕获参数,也尝试将参数作为参数传递,但都卡住了。

我还没有尝试使用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();
}

2
在 lambda 版本中,我看不到 dc 被分配,而在 pthreads 版本中,它被线程专门分配。 - selbie
1
多线程程序在一台机器上出现错误而在另一台机器上却没有这种情况是很常见的。有时候,执行速度上的微小差异就足以导致线程同步不同。 - Piotr Siupa
1
你说你在@MikevanDyke的代码中遇到了同样的问题 - 你能否更新你的问题,展示你正在运行的实际代码?Mike修复了一个真正的错误,但如果还有其他问题,看到你确切拥有的内容将会很有帮助。 - Useless
1
你是如何在容器中启动进程的?如果你使用 docker run -d ubuntu:14.04 /my/process,将其替换为 docker run -d ubuntu:14.04 timeout 10s /my/process 是否可行?它甚至会返回错误代码124告诉你进程超时了。 - root
1
根据你的设计,你需要考虑使用Docker引擎处理所有事情是否值得添加一个线程。以下解决方案中有大约20行非平凡C ++代码,再加上你从stopContainer()和DockerClientpp :: DockerClient构造函数中可能缺失的错误处理。 - root
显示剩余24条评论
2个回答

3

在线程中,您正在访问对变量int *tstd::string *s的引用,这些变量只局限于ContainerManager::execute()方法。一旦ContainerManager::execute()完成,对这两个变量的访问将导致未定义的行为,并且在您的情况下会出现段错误(SEGFAULT)。相反,将这两个指针按值传递给lambda表达式(甚至更好的做法是根本不使用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
@fedemengo,如果您使用join而不是detach,是否仍然会发生这种情况?您尝试过另一个编译器吗? - Mike van Dyke
你的问题似乎仍然只展示了代码,而这些代码根据此答案是明显有错误的。也许你可以编辑一下已经修复的代码,以展示同样的问题? - Useless
@Useless,你的评论是针对fedemengo的,不是吗? - Mike van Dyke
1
确实是这样。由于@在评论线程中不起作用,我会在问题上发表评论... - Useless
@MikevanDyke 只有在分离线程时才会发生这种情况,加入线程则完全正常。 - fedemengo

2
段错误提示我,类正在超出作用域,即使您不希望它这样。另一个可能性是您正在访问的变量存在竞争条件。

尝试通过复制将所有变量传递给lambda,而不是在lambda中捕获this。这将消除与作用域相关的任何竞争条件,并解决任何潜在的生命周期问题,因为lambda将完全与任何其他线程分离。当然,这意味着不能使用指向其他地方的数据的指针或引用,请确保真正复制了timeOutactiveId

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

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

如果您有时间信息,您可以查看问题是否真的是detach或线程引起的。如果您睡眠10秒,并且所有内容都通过复制传递,我希望您可以看到崩溃会立即发生(并且detach导致崩溃)或稍后发生(并且线程在睡眠后导致崩溃)。另一种选择是您根本没有睡眠(t = 0),并且由于stopContainer中的某些原因(可能违反其合同或错误的库代码)而导致崩溃。您还可以删除所有线程代码以使其更简单。有很多选择。 - Joel
如果您查看我的第一次编辑,就会立即发现线程代码的第一行都没有运行。一旦我调用detach,就会出现segfault错误,并且使用gdb时似乎线程为空。 - fedemengo
1
我有点怀疑。Segfault通常与非法访问内存有关。分离本身与此无关。我不愿指责detach。更可能的是detach的结果。首先尝试将线程转换为无操作,以查看是否仍会出现segfault,并添加代码行。在您的第一个示例中,您从未显示控制台输出,因此我无法确定detach本身是否有问题。如果这是竞争条件导致segfault或刷新,则打印语句也不是决定性的,可能总是先出现segfault。 - Joel
1
此外,如果代码使用任何优化编译,我不信任gdb。那时候可能发生任何事情。通常,并行代码在有和没有优化的情况下会表现出不同的行为,因为这会改变时间序列,从而改变行为。这就是为什么调试并行代码对我来说很困难-在调试中似乎有效的东西可能在发布中失败。然后我就不能信任发布版中的调试器,所以我必须退回到其他奥秘的方法。 - Joel
1
我尝试捕获单个变量,现在它正在工作。我百分之百确定我已经尝试过了,但显然我漏掉了什么。我猜detach不是问题。 - fedemengo
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接