[英]No thread-safety between different vectors in C++?
我的问题涉及同时使用不同的向量。 我知道我不能指望同一个向量同时在多个线程中工作。 我已经分解了程序,以便更容易理解它。 我有一个ThreadClass
类,它有一个构造函数,它只是向向量k
添加一个元素,然后调用一个线程toCall
,然后输出应该是 1 的向量的大小。 此类的对象是在main()
函数内使用 vector 的push_back
成员的不同向量内创建的。
结果是 0。有时我也可以得到 1。 如果我切换到调试模式,我可以产生更多的数字 1。 我已经在 gnu C++17 编译器(Ubuntu 16.04)和 Visual Studio 编译器(Windows 10)上测试了这个问题。 我现在的问题是,这个例子是否表明我应该完全避免在多线程程序中使用向量?
class ThreadClass
{
private:
std::vector<int> k;
std::thread thr;
public:
ThreadClass() {
k.push_back(27);
thr = std::thread(&ThreadClass::toCall, this);
}
void toCall() {
std::cout << k.size() << std::endl;
}
void close() {
if (thr.joinable())thr.join();
}
};
int main(){
std::vector<ThreadClass> lols;
lols.push_back(ThreadClass());
lols[0].close();
return 0;
}
问题是ThreadClass
类型的值持有对自身的引用。 具体来说, thr
包含this
的副本。
当您复制或移动这样的值时,例如,当临时ThreadClass()
被移动到lols
,副本会保存一个重复的this
ptr,即它指向旧的临时值,其生命周期在对lols.push_back
的调用完成后结束。
我们可以在没有线程的情况下复制这个问题:
class Foo
{
private:
std::vector<int> k;
Foo* possibly_this;
public:
Foo() {
k.push_back(27);
possibly_this = this;
}
void toCall() {
std::cout << possibly_this->k.size() << std::endl;
}
};
int main(){
std::vector<Foo> lols;
lols.push_back(Foo{});
lols[0].toCall();
}
(对我来说,它在 7.3.1 上用 -O0 打印 0,但同样,它是 UB,所以它可以在你的机器上做任何事情。)
lols.emplace()
不会有帮助。 如果std::vector
调整大小,则所有指向它的指针/迭代器都将失效。 不幸的是,您无法更改存储在thr
的指针,因此只有一种解决方案:禁用ThreadClass
的复制和移动构造函数,如下所示:
//within the definition of ThreadClass
ThreadClass(ThreadClass const&) = delete;
为了将ThreadClass
放置在容器中,您将需要额外的间接级别,以允许ThreadClass
类型的值的实际对象具有稳定的位置。 std::list<ThreadClass>
或std::vector<std::unique_ptr<ThreadClass>>
都可以。
一个问题是您的线程可以在构造函数返回之前调用toCall
。 在构造函数中创建回调对象的线程并不是一个好主意。 将线程创建推迟到某种start
或launch
函数,并在构造函数返回后调用它。
这也是一个问题:
lols.push_back(ThreadClass());
在这里,(临时的)析构函数甚至可以在toCall
之前运行! 那肯定行不通。 这是不在构造函数中创建线程的另一个很好的理由——它使临时对象变得灾难性。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.