今天我遇到了一个有趣的问题,我正在寻找一个“好”的解决方案。我知道原来的问题,但我还是找不到一个不太糟糕的解决方案。
我试图维护一个包装线程的类的std :: vector。到目前为止这很好,就像C ++ 11的移动语义一样,我们能够做到这一点。 无论如何,我的问题在于,由线程执行的函数是线程包装类的成员函数。
这会产生无法预料的问题,由于std :: vector对象内部的可能移动(例如,在内部重新分配存储时),我的包装类的成员移动完美,但线程确实仍然绑定到它被移动的对象。
我把一个小例子拼凑在一起来演示我的问题(见下文)。 但是,我知道三种可能的解决方案:
预先将std :: vector分配给足够大,以避免重新分配
例如workers.reserve(10)
在创建线程之前
con:在“运行时”(产生线程后)不可调整
避免移动工作对象(从而保留线程绑定实例与承载线程的实例的关联),例如通过将它们包装成一个保留其成员的移动启用类型:
例如,将工作人员包裹在std::unique_ptr
中,但是像指针这样的任何东西都可以完成工作
con:由于包装器而浪费资源和可读性,需要按人员分配
将线程绑定函数放在worker的另一个成员对象中,该成员对象未嵌入到worker类中但通过指针引用,从而保留了线程操作的对象并将该对象从worker更改为worker 。
class worker {
struct exec_context {
// the whole execution context is here...
void work() {
// this is the thread bound function
}
};
exec_context *ctx_; // allocate this in default constructor
// move the pointer in move constructor
// ...
};
con:需要按人员分配
如果我错了,请纠正我,但我几乎可以确定问题的根源,但一直在努力寻找一个不会吮吸的解决方案。
编辑我确实希望将std :: vector保持为恒定时间随机访问。
一如既往:非常感谢你的帮助! : - )
谢谢,
塞巴斯蒂安
#include <thread>
#include <vector>
#include <chrono>
#include <iostream>
using namespace std::literals;
class worker
{
public:
// default constructor, used by emplace
worker()
: thread_{&worker::work, this}
{ }
// resource like handlin, forbid copy semantics
worker(const worker&) = delete;
worker& operator=(const worker&) = delete;
// allow for move construction (requirement for std::vector-container)
// will be invoked, when std::vector reallocates its internal storage
worker(worker &&other)
: thread_(std::move(other.thread_)),
run_(other.run_),
num_(other.num_)
{ }
// we don't want (and need) move assign
worker& operator=(worker&&) = delete;
// indicator: if at any time we get the output 0xdead
// we know that we are operating on an already destructed obejct
virtual ~worker()
{
num_ = 0xdead;
}
// for the sake of completness
void stop()
{
run_ = false;
thread_.join();
}
private:
std::thread thread_;
bool run_ = true;
int num_ = 0;
void work()
{
// output num_ continuesly
while (run_) {
std::printf("%x\n", num_);
std::this_thread::sleep_for(500ms);
}
}
};
int main()
{
std::vector<worker> workers;
// One worker is fine, but more instances will end up in reallocating of the underlying storage and thus in move construction.
workers.emplace_back();
//workers.emplace_back();
std::this_thread::sleep_for(5s);
// again, for the sake of completeness...
for (auto &w : workers)
w.stop();
}
确实给出了没有重新分配的0
,但是重新分配了向量内部空间:
0
0
0
dead
dead
dead
dead
dead
dead
dead
dead
0
dead