【问题标题】:Passing variable by reference to several threads corrupts heap通过引用多个线程来传递变量会破坏堆
【发布时间】:2018-01-29 13:55:46
【问题描述】:

这是线程安全测试的一部分。我在不同的线程中运行匿名 lambda。

我使用变量i 作为线程ID。

最初我使用[&] 传递main 范围内的每个变量,但这会破坏堆。

现在通过按值传递i 解决了这个问题,但对于我来说,我无法弄清楚为什么这会导致堆上的问题,因为线程只读取i

谁能解释一下?


产生错误的最小可编译示例:

#include <thread>
#include <vector>

using namespace std;

int main(int argc, char** argv) {
    vector<thread> threads;
    vector<string> vec1;
    vector<string> vec2;
    for (int i = 0; i < 2; i++) {
        threads.push_back(
            thread([&vec1, &vec2, &i]() {
                for (int j = 0; j < 10; j++) {
                    const string str = "foo";
                    if (i == 0) {
                        vec1.push_back(str);
                    } else {
                        vec2.push_back(str);
                    }
                }
            })
        );
    }

    for (auto& thread : threads) {
        thread.join();
    }

    return 0;
}

输出:

*** Error in `/vagrant/bin/TempFileTest': double free or corruption (fasttop): 0x00007f00240008c0 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f002a0e97e5]
/lib/x86_64-linux-gnu/libc.so.6(+0x8037a)[0x7f002a0f237a]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7f002a0f653c]
/vagrant/bin/TempFileTest(_ZNSt6vectorINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESaIS5_EE19_M_emplace_back_auxIJRKS5_EEEvDpOT_+0x1a3)[0x4021b3]
/vagrant/bin/TempFileTest[0x401e83]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(+0xb8c80)[0x7f002a70ac80]
/lib/x86_64-linux-gnu/libpthread.so.0(+0x76ba)[0x7f002a9db6ba]
/lib/x86_64-linux-gnu/libc.so.6(clone+0x6d)[0x7f002a17941d]
======= Memory map: ========
00400000-00403000 r-xp 00000000 08:02 2622834                            /vagrant/bin/TempFileTest
00602000-00603000 r--p 00002000 08:02 2622834                            /vagrant/bin/TempFileTest
00603000-00604000 rw-p 00003000 08:02 2622834                            /vagrant/bin/TempFileTest
02182000-021b4000 rw-p 00000000 00:00 0                                  [heap]
7f001c000000-7f001c021000 rw-p 00000000 00:00 0 
7f001c021000-7f0020000000 ---p 00000000 00:00 0 
7f0024000000-7f0024021000 rw-p 00000000 00:00 0 
7f0024021000-7f0028000000 ---p 00000000 00:00 0 
7f0028d67000-7f0028d68000 ---p 00000000 00:00 0 
7f0028d68000-7f0029568000 rw-p 00000000 00:00 0 
7f0029568000-7f0029569000 ---p 00000000 00:00 0 
7f0029569000-7f0029d69000 rw-p 00000000 00:00 0 
7f0029d69000-7f0029e71000 r-xp 00000000 00:32 313                        /lib/x86_64-linux-gnu/libm-2.23.so
7f0029e71000-7f002a070000 ---p 00108000 00:32 313                        /lib/x86_64-linux-gnu/libm-2.23.so
7f002a070000-7f002a071000 r--p 00107000 00:32 313                        /lib/x86_64-linux-gnu/libm-2.23.so
7f002a071000-7f002a072000 rw-p 00108000 00:32 313                        /lib/x86_64-linux-gnu/libm-2.23.so
7f002a072000-7f002a232000 r-xp 00000000 00:32 45                         /lib/x86_64-linux-gnu/libc-2.23.so
7f002a232000-7f002a432000 ---p 001c0000 00:32 45                         /lib/x86_64-linux-gnu/libc-2.23.so
7f002a432000-7f002a436000 r--p 001c0000 00:32 45                         /lib/x86_64-linux-gnu/libc-2.23.so
7f002a436000-7f002a438000 rw-p 001c4000 00:32 45                         /lib/x86_64-linux-gnu/libc-2.23.so
7f002a438000-7f002a43c000 rw-p 00000000 00:00 0 
7f002a43c000-7f002a452000 r-xp 00000000 00:32 314                        /lib/x86_64-linux-gnu/libgcc_s.so.1
7f002a452000-7f002a651000 ---p 00016000 00:32 314                        /lib/x86_64-linux-gnu/libgcc_s.so.1
7f002a651000-7f002a652000 rw-p 00015000 00:32 314                        /lib/x86_64-linux-gnu/libgcc_s.so.1
7f002a652000-7f002a7c4000 r-xp 00000000 00:32 311                        /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7f002a7c4000-7f002a9c4000 ---p 00172000 00:32 311                        /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7f002a9c4000-7f002a9ce000 r--p 00172000 00:32 311                        /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7f002a9ce000-7f002a9d0000 rw-p 0017c000 00:32 311                        /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7f002a9d0000-7f002a9d4000 rw-p 00000000 00:00 0 
7f002a9d4000-7f002a9ec000 r-xp 00000000 00:32 61                         /lib/x86_64-linux-gnu/libpthread-2.23.so
7f002a9ec000-7f002abeb000 ---p 00018000 00:32 61                         /lib/x86_64-linux-gnu/libpthread-2.23.so
7f002abeb000-7f002abec000 r--p 00017000 00:32 61                         /lib/x86_64-linux-gnu/libpthread-2.23.so
7f002abec000-7f002abed000 rw-p 00018000 00:32 61                         /lib/x86_64-linux-gnu/libpthread-2.23.so
7f002abed000-7f002abf1000 rw-p 00000000 00:00 0 
7f002abf1000-7f002ac17000 r-xp 00000000 00:32 42                         /lib/x86_64-linux-gnu/ld-2.23.so
7f002adf6000-7f002adfc000 rw-p 00000000 00:00 0 
7f002ae15000-7f002ae16000 rw-p 00000000 00:00 0 
7f002ae16000-7f002ae17000 r--p 00025000 00:32 42                         /lib/x86_64-linux-gnu/ld-2.23.so
7f002ae17000-7f002ae18000 rw-p 00026000 00:32 42                         /lib/x86_64-linux-gnu/ld-2.23.so
7f002ae18000-7f002ae19000 rw-p 00000000 00:00 0 
7fff30694000-7fff306b5000 rw-p 00000000 00:00 0                          [stack]
7fff30761000-7fff30763000 r--p 00000000 00:00 0                          [vvar]
7fff30763000-7fff30765000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
Aborted (core dumped)

没有错误的最小可编译示例(注意在i 上没有&amp;):

#include <thread>
#include <vector>

using namespace std;

int main(int argc, char** argv) {
    vector<thread> threads;
    vector<string> vec1;
    vector<string> vec2;
    for (int i = 0; i < 2; i++) {
        threads.push_back(
            thread([&vec1, &vec2, i]() {
                for (int j = 0; j < 10; j++) {
                    const string str = "foo";
                    if (i == 0) {
                        vec1.push_back(str);
                    } else {
                        vec2.push_back(str);
                    }
                }
            })
        );
    }

    for (auto& thread : threads) {
        thread.join();
    }

    return 0;
}

我正在使用:

Ubuntu 16.04

gcc 5.4.0

【问题讨论】:

  • 试图在没有任何同步的情况下同时修改相同的变量会导致问题发生。
  • @Someprogrammerdude 但两个线程都没有修改i
  • 您忘记了 第三个 线程:创建另外两个线程的那个。它确实修改了i,您根本不知道或控制三个线程中的任何一个何时运行以及i 在两个子线程中的任何一个中具有什么值。跨度>
  • 当您通过引用捕获时,最好问问自己“什么管理这个对象的生命周期?”
  • 除此之外,由于您在没有同步的情况下 push_back 进入向量,因此您的两个代码示例都表现出未定义的行为。所以关于哪个代码更难失败以及为什么失败的整个问题是没有实际意义的。

标签: c++ multithreading c++11 lambda pass-by-reference


【解决方案1】:

i 的值在每个迭代循环(在主线程中)发生变化,而您在其他线程中读取它(没有同步)-> UB。

此外,一旦主循环结束,您就有对i 的悬空引用。

【讨论】:

    【解决方案2】:

    附带说明一下,如果您只是有条件地捕获向量本身,您可以省去很多麻烦并减少代码大小:

    for (int i = 0; i < 2; i++) {
        auto& vec = (i == 0 ? vec1 : vec2);
        threads.push_back(
            thread([&vec]() {
                for (int j = 0; j < 10; j++) {
                    const string str = "foo";
                    vec.push_back(str);
                }
            })
        );
    }
    

    【讨论】:

      【解决方案3】:

      for 和线程都使用i 的相同内存地址(因为您通过引用传递它)。正确的做法是让线程拥有自己的i 副本,这对于线程的生命周期是相同的,并且与循环更改无关。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-02-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-12-28
        • 1970-01-01
        相关资源
        最近更新 更多