【发布时间】:2021-10-02 18:26:57
【问题描述】:
在下面的代码中,Bar 应该为创建成本适中的线程不安全对象建模。 Foo 包含 Bar 并且是多线程的,因此它使用 thread_specific_ptr<Bar> 来创建每个线程 Bar,可以在多次调用 loop 时重复使用相同的 Foo(因此摊销为每个线程创建Bar 的成本)。 Foo 总是创建一个具有相同 num 的 Bar,因此完整性检查应该总是通过,但它失败了。
原因(我认为)在requirement for the thread_specific_ptr destructor中解释:
与此 thread_specific_ptr 关联的所有线程特定实例(可能与此线程关联的实例除外)必须为空。
所以问题是由三件事共同引起的:
-
Bar在工作线程中创建的对象在Foosthread_specific_ptr被清理时不会被清理,因此会在main中的循环迭代中持续存在(本质上是内存泄漏) - C++ 运行时在
main中循环的迭代之间重用for_each中的线程 - C++ 运行时将
main循环中的每个Foo重新分配到相同的内存地址
thread_specific_ptrs 的索引方式(通过thread_specific_ptr 的内存地址和线程 ID)导致旧的Bars 被意外重用。我理解这个问题;我不明白该怎么做。请注意文档中的注释:
要求是因为为了删除所有这些实例,应该强制实现维护具有关联特定 ptr 的所有线程的列表,这与线程特定数据的目标背道而驰。
我也想避免这种复杂性。
如何使用for_each进行简单的线程管理,同时避免内存泄漏?解决方案要求:
- 每个
Foo的每个线程应该只创建一个Bar(即,不要在for_each内创建新的Bar) - 假设
Bar不是线程安全的。 - 如果可能,使用
for_each使并行循环尽可能简单 - 循环实际上应该并行运行(即,单个
Bar周围没有互斥锁) -
由
loop创建的Bar对象应该可以使用,直到创建它们的Foo对象被破坏,此时所有Bar对象也应该被破坏。
以下代码在具有足够内核的机器上编译并应以高概率退出并返回代码 1。
#include <boost/thread/tss.hpp>
#include <execution>
#include <iostream>
#include <vector>
using namespace std;
class Bar {
public:
// models a thread-unsafe object
explicit Bar(int i) : num(i) { }
int num;
};
class Foo {
public:
explicit Foo(int i) : num(i) { }
void loop() {
vector<int> idxs(32);
iota(begin(idxs), end(idxs), 0);
for_each(__pstl::execution::par, begin(idxs), end(idxs), [&](int) {
if (ptr.get() == nullptr) {
// no `Bar` exists for this thread yet, so create one
Bar *tmp = new Bar(num);
ptr.reset(tmp);
}
// Get the thread-local Bar
Bar &b = *ptr;
// Sanity check: we ALWAYS create a `Bar` with the same num as `Foo`;
// see the `if` block above.
// Therefore, this condition shouldn't ever be true (but it is!)
if (b.num != num) {
cout << "NOT THREAD SAFE: Foo index is " << num << ", but Bar index is " << b.num << endl;
exit(1);
}
});
}
boost::thread_specific_ptr<Bar> ptr;
int num;
};
int main() {
for(int i = 0; i < 100; i++) {
Foo f(i);
f.loop();
}
return 0;
}
【问题讨论】:
-
条形图的寿命是多少?它们必须具有已定义的生命周期,并且您必须在您创建的每个线程中清理每个线程特定的 Bar。
-
理想情况下,由单个
Foo创建的所有Bars 都应在清理Foo时清理(无论与Bar关联的线程是否已清理向上)。我最初认为thread_specific_ptr的析构函数会这样做,但文档明确指出并非如此。 -
另外,看在上帝的份上,不要
using namespace std; using namespace boost;- 你是否希望唯一能理解你的代码的人是拥有整个std、所有clang扩展和所有boost的人,所以他们可以算出你打电话给哪个?我怀疑唯一的提升是线程特定的,但它实际上需要撤消那些使用命名空间来确保。 -
不能用原生的 C++11
thread_local代替吗? -
@JDługosz 如果您认为它会起作用,请写一个答案...
标签: c++ multithreading boost