[英]C++ background thread in class - instance scope
我有这个简单的 class:
struct Foo {
void Run() {
this->bgLoader = std::thread([this]() mutable {
//do something
this->onFinish_Thread();
});
}
std::function<void()> onFinish_Thread;
std::thread bgLoader;
};
这是从 C-API 调用的:
void CApiRunFoo(){
Foo foo;
foo.onFinish_Thread = []() {
//do something at thread end
};
foo.Run();
}
我想运行CApiRunFoo
,从它返回但保持线程运行直到它完成。
现在,问题是,一旦CApiRunFoo
结束,即使后台线程仍在运行, foo 也会退出 scope 。 如果我通过new
将foo
更改为 object ,它将运行,但会导致 memory 泄漏。
我正在考虑使用以下方法创建析构函数:
~Foo(){
if (bgLoader.joinable()){
bgLoader.join();
}
}
但我不确定它是否会导致死锁,而且它可能不会导致CApiRunFoo
返回,直到线程完成。
这个问题有什么解决方案/设计模式吗?
您可以将Foo
实例返回给 C 程序:
struct Foo {
~Foo() {
if (bgLoader.joinable()) {
run = false;
bgLoader.join();
}
}
void Run() {
run = true;
this->bgLoader = std::thread([this]() mutable {
while(run) {
// do stuff
}
this->onFinish_Thread();
});
}
std::atomic<bool> run;
std::function<void()> onFinish_Thread;
std::thread bgLoader;
};
C接口:
extern "C" {
struct foo_t {
void* instance;
};
foo_t CApiRunFoo() {
Foo* ptr = new Foo;
ptr->onFinish_Thread = []() {
std::cout << "done\n";
};
ptr->Run();
return foo_t{ptr};
}
void CApiDestroyFoo(foo_t x) {
auto ptr = static_cast<Foo*>(x.instance);
delete ptr;
}
}
还有一个 C 程序:
int main() {
foo_t x = CApiRunFoo();
CApiDestroyFoo(x);
}
看起来您希望Foo
对象在线程完成时自动自毁,您可以分离运行它们并让它们delete this;
完成后。
#include <atomic>
#include <condition_variable>
#include <cstdint>
#include <iostream>
#include <functional>
#include <mutex>
#include <thread>
// Counting detached threads and making sure they are all finished before
// exiting the destructor. Used as a `static` member of `Foo`.
struct InstanceCounter {
~InstanceCounter() {
run = false;
std::unique_lock lock(mtx);
std::cout << "waiting for " << counter << std::endl;
while(counter) cv.wait(lock);
std::cout << "all done" << std::endl;
}
void operator++() {
std::lock_guard lock(mtx);
std::cout << "inc: " << ++counter << std::endl;
}
void operator--() {
std::lock_guard lock(mtx);
std::cout << "dec: " << --counter << std::endl;
cv.notify_one(); // if the destructor is waiting
}
std::atomic<bool> run{true};
std::mutex mtx;
std::condition_variable cv;
unsigned counter = 0;
};
struct Foo {
bool Run() {
try {
++ic; // increase number of threads in static counter
bgLoader = std::thread([this]() mutable {
while(ic.run) {
// do stuff
}
// if onFinish_Thread may throw - you may want to try-catch:
onFinish_Thread();
--ic; // decrease number of threads in static counter
delete this; // self destruct
});
bgLoader.detach();
return true; // thread started successfully
}
catch(const std::system_error& ex) {
// may actually happen if the system runs out of resources
--ic;
std::cout << ex.what() << ": ";
delete this;
return false; // thread not started
}
}
std::function<void()> onFinish_Thread;
private:
~Foo() { // private: Only allowed to self destruct
std::cout << "deleting myself" << std::endl;
}
std::thread bgLoader;
static InstanceCounter ic;
};
InstanceCounter Foo::ic{};
现在 C 界面变得更像您在问题中所拥有的。
#include <stdbool.h>
extern "C" {
bool CApiRunFoo() {
Foo* ptr = new Foo;
ptr->onFinish_Thread = []() {
std::cout << "done" << std::endl;
};
return ptr->Run();
// it looks like `ptr` is leaked here, but it self destructs later
}
}
您的程序应该在将来的某个时候调用join
并完成新线程(另请参见这个问题和答案)。 为此,它应该持有对线程 object 的引用(在广义上)。 在您当前的设计中,您的foo
就是这样的参考。 所以你不能失去它。
您应该考虑一个有意义的地方join
您的代码。 这同一个地方应该保存你的foo
。 如果你这样做,没有问题,因为foo
还包含onFinish_Thread
object。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.