C++ 线程之间的内存共享

Posted

技术标签:

【中文标题】C++ 线程之间的内存共享【英文标题】:Memory sharing between C++ threads 【发布时间】:2012-07-06 07:47:58 【问题描述】:

我是 C++ 线程的新手,我试图清楚地了解内存是如何在线程之间共享/不共享的。我在 C++11 中使用std::thread。 根据我在其他 SO 问题上阅读的内容,stack 内存仅由一个线程拥有,heap 内存在线程之间共享。因此,根据我对堆栈与堆的理解,以下应该是正确的:

#include <thread>
using namespace std;

class Obj 
public:
    int x;
    Obj()x = 0;
;

int main() 
    Obj stackObj;
    Obj *heapObj = new Obj();
    thread t([&]
        stackObj.x++;
        heapObj->x++;
    );
    t.join();
    assert(heapObj->x == 1);
    assert(stackObj.x == 0);

如果我搞砸了一堆东西,请原谅我,lambda 语法对我来说很新。但希望我正在尝试做的是连贯的。 这会按我的预期执行吗?如果不是,我误会了什么?

【问题讨论】:

前提有点不对。堆栈内存不是线程本地的,它可以在线程之间共享。 请查看此链接以获得更详细的答案:candrews.net/blog/2011/07/understanding-c-0x-lambda-functions - 因为您使用了[&amp;],所以所有变量都是引用的,所以两个计数器都是1。如果您使用[=],您的断言会成功。 @juanchopanza 我的错,我知道线程局部具有特定含义(尽管我不完全确定它是什么),所以我改变了我的问题。这就是“前提”的问题吗? 问题是栈内存不属于一个线程,它可以被任意数量的线程访问。 【参考方案1】:

记忆就是记忆。 C++ 中的对象占用内存中的某个位置;该位置可能在堆栈或堆上,或者它可能已被静态分配。对象的位置无关紧要:任何具有对象引用或指针的线程都可以访问该对象。如果两个线程有​​一个对象的引用或指针,那么两个线程都可以访问它。

在您的程序中,您创建一个工作线程(通过构造一个std::thread)来执行您提供的 lambda 表达式。因为您通过引用捕获了stackObjheapObj(使用[&amp;] 捕获默认值),所以该lambda 引用了这两个对象。

这些对象都位于主线程的堆栈上(注意heapObj 是一个指针类型的对象,位于主线程的堆栈上,指向位于堆上的动态分配的对象)。不制作这些对象的副本;相反,您的 lambda 表达式具有对对象的引用。它直接修改stackObj,间接修改heapObj指向的对象。

主线程加入工作线程后,heapObj-&gt;xstackObj.x 的值都是1


如果您使用了值捕获默认值 ([=]),您的 lambda 表达式将会复制stackObjheapObj。 lambda 表达式中的表达式stackObj.x++ 将增加副本,而您在main() 中声明的stackObj 将保持不变。

如果通过值捕获heapObj,则只复制指针本身,因此在使用指针的副本时,它仍然指向同一个动态分配的对象。表达式heapObj-&gt;x++ 将取消引用该指针,产生您通过new Obj() 创建的Obj,并增加它的值。然后,您会在 main() 的末尾观察到 heapObj-&gt;x 已递增。

(请注意,要修改按值捕获的对象,必须将 lambda 表达式声明为mutable。)

【讨论】:

如果“记忆就是记忆”,这一切是怎么回事? ***.com/questions/1665419/do-threads-share-the-heap @MichaelDorst:每个线程都有自己的堆栈,是的。只有拥有堆栈的线程才能在该堆栈上分配内存。因此,在您的程序中,主线程和工作线程都有自己独立的堆栈。但是,没有什么可以阻止线程与另一个线程共享指向它在自己的堆栈上分配的对象的指针或引用。这正是您在这里所做的:主线程在其堆栈上创建一个Obj 对象,然后与工作线程共享对该对象的引用。然后两个线程都可以访问该对象。 哦,好吧,这更有意义。 对该问题的公认答案是“每个线程都有一个私有堆栈”。确实如此,但 private 的意思是“只有拥有堆栈的线程才能在其上创建和销毁对象”。这并不意味着“没有其他线程可以访问此内存。” 顺便说一下,将堆栈上的对象共享给其他线程是一种不好的做法。堆栈上的对象一直存在,直到范围关闭。当对象已经被销毁时,可能会遇到这种情况,但您仍在尝试从另一个线程使用它。最好复制或移动堆引用,除非您绝对确定存储对象的线程在其他线程摆脱堆栈引用之前不会进一步前进。【参考方案2】:

我同意 James McNellis 的观点,即 heapObj-&gt;xstackObj.x 将是 1

此外,此代码有效,因为您在生成线程后立即join。如果你启动了线程,然后在它运行时做了更多的工作,异常可能会展开堆栈,突然新线程的stackObj 无效。这就是为什么在线程之间共享堆栈内存不是一个好主意,即使它在技术上是可行的。

【讨论】:

感谢您对我的警告,但您所说的对我来说真的没有意义。究竟什么是“展开堆栈”,为什么会发生这种情况? 当 C++ 程序抛出异常时,运行时会调用当前栈帧中每个对象的析构函数,然后弹出栈帧并重复该过程,直到捕获到异常为止。 ***.com/questions/2331316/what-is-stack-unwinding

以上是关于C++ 线程之间的内存共享的主要内容,如果未能解决你的问题,请参考以下文章

C++ 和 Java 进程之间的共享内存

IPC共享内存和线程内存的性能差异

硬件应用程序使用共享内存 (C++) 时需要互斥锁

两个线程之间的列表共享

Java内存模型

C ++线程:尽管没有种族,但共享内存未更新