首页 > 解决方案 > 类中的 C++ 后台线程 - 实例范围

问题描述

我有这个简单的课程:

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 也会超出范围。如果我foo通过 更改为对象new,它将运行,但会导致内存泄漏。

我正在考虑使用以下方法创建析构函数:

~Foo(){
    if (bgLoader.joinable()){
        bgLoader.join();
    }
}

但我不确定它是否会导致死锁,而且CApiRunFoo在线程完成之前它可能不会导致返回。

这个问题有什么解决方案/设计模式吗?

标签: c++multithreading

解决方案


您可以将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
}

}

演示


推荐阅读