C++ 标准是否提供了关于线程堆栈的非重叠性质的保证(如由一个线程启动)std::thread
)?特别是,是否可以保证线程在线程堆栈的进程地址空间中拥有自己的、独占的、分配的范围?标准中哪里描述了这一点?
例如
std::uintptr_t foo() {
auto integer = int{0};
return std::bit_cast<std::uintptr_t>(&integer);
...
}
void bar(std::uint64_t id, std::atomic<std::uint64_t>& atomic) {
while (atomic.load() != id) {}
cout << foo() << endl;
atomic.fetch_add(1);
}
int main() {
auto atomic = std::atomic<std::uint64_t>{0};
auto one = std::thread{[&]() { bar(0, atomic); }};
auto two = std::thread{[&]() { bar(1, atomic); }};
one.join();
two.join();
}
这可以两次打印相同的值吗?感觉标准应该在某个地方提供这种保证。但不确定..
C++ 标准甚至不要求使用堆栈来实现函数调用(或者线程在这个意义上具有堆栈)。
当前的 C++ 草案是这样说的重叠的物体 http://eel.is/c++draft/intro.object#9:
如果一个对象嵌套在另一个对象中,或者如果至少一个对象是零大小的子对象并且它们属于不同类型,则两个具有重叠生命周期(不是位域)的对象可能具有相同的地址;否则,它们具有不同的地址并占用不相交的存储字节。
在(非规范性)脚注中:
根据“as-if”规则,如果程序无法观察到差异([介绍执行 http://eel.is/c++draft/intro.execution]).
在您的示例中,我认为线程没有按照预期正确同步,因此线程的生命周期integer
对象不一定重叠,因此两个对象可以放在同一地址。
如果代码已修复以正确同步并且foo
被手动内联到bar
,以这样的方式integer
当打印其地址时,对象仍然存在,那么必须有两个对象分配在不同的地址,因为差异is可观察到的。
然而,这些都不能告诉您堆栈协程是否可以在没有编译器帮助的情况下在 C++ 中实现。现实世界的编译器对执行环境做出的假设未反映在 C++ 标准中,仅由 ABI 标准暗示。与堆栈切换协程特别相关的是,线程描述符和线程局部变量的地址在执行函数时不会改变(因为它们的计算成本可能很高,并且编译器会发出代码将它们缓存在寄存器或堆)。
这就是可能发生的情况:
协程在线程A上运行并访问errno
.
协程从线程 A 中挂起。
协程在线程 B 上恢复。
协程访问errno
again.
此时,线程B将访问errno
线程 A 的值,此时线程 A 很可能正在做一些完全不同的事情。
如果协程仅在其挂起的同一线程上恢复,则可以避免此问题,这是非常严格的,并且可能不是大多数协程库作者所考虑的。最糟糕的是,在大多数情况下,在错误的线程上恢复可能会起作用,因为一些广泛使用的线程局部变量(例如errno
)不完全是线程本地的,不会立即导致明显有错误的程序。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)