C++标准库容器(如std::queue)是否保证可重入?

11

我看到有人建议如果我想要使用标准容器,例如std::queue和std::vector,就应该将它们包装在互斥锁或类似的东西中。

我理解为需要针对每个被多个线程访问的容器实例都需要一个锁,而不是每种类型或任何使用c++标准库。但是这假定标准容器和标准库可以保证可重入性。

那么语言本身是否有这样的保证呢?


1
没有,就这样。 - Sam Varshavchik
3
重新进入(Reentrancy)只会影响那些可以调用自己的函数,而要达到这一点需要付出相当大的努力(例如使用某种恶意构造函数)。 - Kerrek SB
3
第一次阅读此内容时,我的想法与@KerrekSB类似。第二次阅读时,我意识到这是一个比我想象中更好的问题。您正确地指出了可重入性不能使对象线程安全,但是如果没有它,您无法在没有并发控制的情况下使用来自不同线程的两个不同的“独立”类实例。 - Avi Berger
我手头没有标准的副本可以给出明确的答案,但在网上找到了这个链接:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2669.htm。我相信答案最终会是,在C++11或14之前,标准并不保证它是正确的,但在实践中早期就是这种情况。(请注意,这不适用于被C++“继承”的C库例程。) - Avi Berger
2
@Max:是的,当然可以,但主要问题在于可重入性和线程安全是正交概念。你可以有非可重入的、线程安全的函数,也可以有可重入的、线程不安全的函数。 - Kerrek SB
显示剩余2条评论
2个回答

9
标准规定如下:
除非在本标准中明确指定,否则实现定义了哪些标准C++库函数可以递归地被调用。
然后它继续规定,在零个情况下必须重新进入一个函数。
如果严格遵循这个标准,那么标准库的使用范围会变得相当有限。许多库函数调用用户提供的函数。编写这些函数的人,特别是那些作为库发布的函数,通常不知道它们将从哪里调用。
完全可以假设例如任何构造函数都可以从任何标准容器的emplace_back中调用;如果用户希望消除任何不确定性,则必须在任何构造函数中避免对emplace_back的任何调用。任何复制构造函数都可以从例如vector::resize或sort中调用,因此无法管理向量或进行排序。等等,自由发挥。
这包括调用任何合理使用标准库的第三方组件。
所有这些限制加在一起可能意味着标准库的大部分内容根本无法在实际程序中使用。
更新:这甚至没有考虑到线程问题。在多个线程中,至少处理容器和算法的函数必须重新进入。想象一下std::vector::operator[]不可重入。这意味着不能从两个不同的线程同时访问两个不同的向量!这显然不是标准的意图。我理解这是您的主要关注点。再次强调,不,我认为没有重入保证;不,我认为缺乏这种保证在任何方面都是不合理的。---更新结束。
我的结论是,这可能是一个疏忽。标准应该要求所有标准函数必须是可重入的,除非另有规定。
我会:
完全忽略任何标准函数不可重入的可能性,除非明确指出该函数无法合理地被重入。
向标准委员会提出问题。

有没有保证,例如标准库不包含在多个标准函数调用中使用的全局访问整数。即意思是,有没有保证没有内存访问重叠?编辑:我刚意识到,这属于可重入性类别。我的错。 - Max
谢谢,这澄清了你的立场并提出了一个建议的政策,使进一步的辩论成为可能。 - Max
不要阅读(过时的)建议措辞,阅读讨论 :) - T.C.
@T.C. 嗯,我不明白这个讨论提出了什么解决方案。除非“函数”指的是“同一对象的所有非静态成员函数”,而不同的对象有不同的函数。那么,至少可以递归地或从不同的线程中访问不同的对象。但是非成员函数呢?为什么不能在sort内部使用sort - n. m.
@T.C. 不用等了,他们已经看到了问题并将其移动到“开放”状态,还没有完全失去希望。 - n. m.
显示剩余2条评论

3
< p > [该答案已留作历史参考,但请查看n.m.的答案。对于个别函数没有要求,但存在单个全局非要求] < /p > < p > 是的,标准保证了标准容器成员函数的可重入性。 < /p > < p > 让我定义一下函数的(不)可重入性。可重入函数可以在一个线程上被调用,并且具有良好定义的行为,即使它已经在该线程的调用堆栈中执行。显然,这只能发生在控制流通过函数调用暂时离开可重入函数的情况下。如果行为没有良好定义,则该函数不是可重入的。 < /p > < p > (叶子函数不能被称为可重入或不可重入,因为控制流只能通过返回离开叶子函数,但这并不影响分析)。< /p > < p > 例子:< /p >
int fac(int n) { return n==0 ? 1 : n * fac(n-1); }
fac(3) 的行为是返回 6,即使 fac(4) 正在运行。因此,fac 是可重入的。
C++ 标准确实定义了标准容器的成员函数的行为。它还定义了所有保证此类行为的限制。标准容器的任何成员函数都没有关于可重入性的限制。因此,任何会限制可重入性的实现都不符合规范。

你能引用一下你所参考的规范章节吗?“C++标准确实定义了标准容器成员函数的行为。它还定义了保证此类行为的所有限制。” - Merlyn Morgan-Graham
1
@MerlynMorgan-Graham:引用所有定义成员函数的部分?那基本上是标准的后半部分,在定义C++库如何整合C库的部分之后。或者你是指标准本身与该标准的实现之间的关系,以及实现符合标准意味着什么?因为这是标准的第一部分(特别是1.4)。最具体但非规范性的是,请参见17.5 [structure.elements]及其脚注。这明确了在未说明要求时的含义。 - MSalters
1
也许我没有正确理解你的回答,或者我错误地阅读了我找到的标准的随机副本,但是 17.5.5.8 确实似乎让函数重新进入成为实现定义。"除非在此标准中明确指定,否则 C++ 标准库中的哪些函数可以递归重入是实现定义的。"编辑:我刚刚看到了你的编辑,所以我可能会终止这个线程 :) - Merlyn Morgan-Graham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接