C++ OpenMP与shared_ptr一起使用

5
这里有一个让我困扰的最小化示例。
#include <iostream>
#include <memory>
#include"omp.h"

class A{
    public:
        A(){std::cout<<this<<std::endl;}
};

int main(){
#pragma omp parallel for 
    for(unsigned int i=0;i<4;i++){
        std::shared_ptr<A> sim(std::make_shared<A>());
    }
    for(unsigned int i=0;i<4;i++){
        std::shared_ptr<A> sim(std::make_shared<A>());
    }
}

如果我运行那段代码几次,可能会得到以下这种结果:
0xea3308
0xea32d8
0xea3338
0x7f39f80008c8
0xea3338
0xea3338
0xea3338
0xea3338

我意识到的是,最后4个输出总是具有相同数量的字符(8个)。但由于某种原因(并非总是如此),前四个输出中的一个或多个包含更多的字符(14个)。看起来使用openmp改变了指针的“性质”(这是我天真的理解)。但这种行为是否正常?我应该期望一些奇怪的行为吗?
编辑
这里是一个实时测试,显示代码的稍微复杂版本中出现了相同的问题。

5
发帖后5秒钟就被踩了?为什么? - PinkFloyd
2个回答

3
这种行为是完全合理的,让我们看看发生了什么。
串行循环
在每次迭代中,你都会得到一个在堆上创建的A对象,同时还有一个被销毁。这些操作按以下顺序排列:
1. 构造 2. 销毁 3. 构造 4. 销毁 5. ...(以此类推)
由于A对象是在堆上创建的,它们会通过内存分配器进行分配。当内存分配器收到请求以获取新的内存时(如步骤3),它将(在许多情况下)首先查看最近释放的内存。它看到最后一次操作是恰好释放了正确大小的内存(步骤2),因此将再次获取该块内存。这个过程将在每次迭代中重复。因此,串行循环通常会给你相同的地址。
并行循环
现在让我们考虑并行循环。由于没有同步,内存分配和释放的顺序是不确定的。因此,它们可以以任何你想象得到的方式交错。因此,内存分配器通常无法使用与上次相同的技巧来始终提供相同的内存块。例如,所有四个A对象都在销毁之前被构造 - 类似这样的顺序:
1. 构造 2. 构造 3. 构造 4. 构造 5. 销毁 6. 销毁 7. 销毁 8. 销毁
因此,内存分配器必须在可以获得一些内存并开始回收之前提供4个全新的内存块。
基于堆栈的版本的行为略微更可预测,但可能取决于编译器优化。对于串行版本,每次创建/销毁对象时,堆栈指针都会进行调整。由于中间没有发生任何事情,它将继续在相同的位置创建。对于并行版本,在共享内存系统中,每个线程都有自己的堆栈。因此,每个线程都会在不同的内存位置上创建自己的对象,而无法进行“回收”。
你看到的行为在任何情况下都不奇怪或者保证。它取决于你拥有的物理核心数量、运行的线程数、使用的迭代数等通常的运行时条件。
最重要的是:一切都很好,你不应该过多地阅读它。

1
非常好的答案,谢谢。我现在明白为什么在串行版本中它总是打印相同的东西,而在并行版本中不是。但这并不能解释为什么第四个cout包含14个字符,而前三个只有8个字符(来自我的示例)。指针的短名称和长名称有什么含义? - PinkFloyd
1
那只是内存中的位置。堆栈位于内存空间的顶部并向下增长。较长的地址意味着堆位置在靠近堆栈的某个地方被找到。 - jepio

1

我认为这取决于您的环境,这并不相关,也不应被视为奇怪的行为。 使用MS VS 2015预览版,您的代码在启用OMP时给出以下结果:

0082C3DC
0082C41C   
0082C49C                                       
0082C45C                                       
0082C41C                                       
0082C41C                                       
0082C41C                   
0082C41C 

大多数时候我得到这样的结果,但有时我会得到我在问题中展示的那种结果...你尝试运行我的代码多少次了? - PinkFloyd
刚刚做了10次,我从未遇到过你的行为。你用的是什么?我会尝试使用GCC。 - coincoin
1
我确实使用gcc 4.9得到了您的行为,但我不认为它可以被视为奇怪的行为。内存地址仍然有效。 - coincoin
我认为这不会成为问题,但最好让其他人确认一下 :) - coincoin

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