C ++中不同向量之间没有线程安全?
Posted
技术标签:
【中文标题】C ++中不同向量之间没有线程安全?【英文标题】:No thread-safety between different vectors in C++? 【发布时间】:2019-07-12 21:07:49 【问题描述】:我的问题是同时使用不同的向量。我知道我不能指望同一个向量同时在多个线程中工作。我已经分解了程序,以便更容易理解它。我有一个ThreadClass
类,它有一个构造函数,它只是将一个元素添加到向量k
,然后调用一个线程toCall
,然后输出应该是一个向量的大小。此类的对象是使用向量的push_back
成员在main()
函数内部的不同向量内创建的。
输出结果为 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;
【问题讨论】:
【参考方案1】:问题在于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>>
都可以解决问题。
【讨论】:
哦,非常感谢,这个解释启发了我。【参考方案2】:一个问题是您的线程可以在构造函数返回之前调用toCall
。在构造函数中创建回调到对象的线程不是一个好主意。将线程创建推迟到某种start
或launch
函数,并在构造函数返回后调用。
这也是个问题:
lols.push_back(ThreadClass());
在这里,(临时的)析构函数甚至可以在toCall
被调用之前运行!那肯定行不通。这是不在构造函数中创建线程的另一个很好的理由——它使临时对象成为灾难。
【讨论】:
谢谢。我接受了你的建议,现在效果很好。但我不确定你的第二部分。据我了解,总是会在调用线程之前调用析构函数。例如,您有一个名为CallMyThread()
的函数,它调用对象内部的一个线程。然后突然无论什么原因都会调用析构函数。
@SouthParkGermany 那么也许析构函数应该确保线程在返回之前完成。以上是关于C ++中不同向量之间没有线程安全?的主要内容,如果未能解决你的问题,请参考以下文章
如果没有插入并且只有 .find(),在多个线程上使用 C++ STL 容器是不是安全?