c++中多线程操作 string 引发的 coredump,栈中比较奇怪的一点

2021-02-03 20:23:24 +08:00
 blacksmith

源代码如下:

#include <iostream>
#include <string>
#include <vector>
#include <thread>

struct A {
    std::string name = "blacksmith";
    int age = 100;
};

struct B1 {
    std::string local_name = "jd-B1";

    void func1(A* a) {
        while (true) {
            a->name = local_name;
            std::string key = local_name + "**";
        }
    }
};

struct B2 {
    std::string local_name = "jd-B2";

    void func1(A* a) {
        while (true) {
            a->name = local_name;
            std::string key = local_name + "**";
        }
    }
};


int main() {
    /**
     * 探测是否支持 COW
     */
    std::string* test = new std::string("blacksmith");
    std::string name = *test;
    std::cout << "test:" << test->data() << ", name=" << name.data() << std::endl;
    if (test->data() == name.data()) {
        std::cout << "COW(Copy On Write) support!" << std::endl;
    } else {
        std::cout << "COW(Copy On Write) NOT support!" << std::endl;
    }
    delete test;


    /**
     * 多线程操作
     */
    std::vector<std::thread> th_vec;
    int thread_count = 4;
    A a;
    B1 b1;
    B2 b2;
    for (int i = 0; i < thread_count; i++) {
        th_vec.emplace_back([&](){
            b1.func1(&a);
        });
        th_vec.emplace_back([&](){
            b2.func1(&a);
        });
    }

    for (auto& item : th_vec) {
        item.join();
    }

    std::cout << "=========END==========" << std::endl;

    return 0;
}

编译:

g++ --std=c++11 string-test.cc -g -lpthread

查看 coredump 栈:

(gdb) bt
#0  0x00007f5613350e20 in __memcpy_ssse3 () from /usr/lib64/libc.so.6
#1  0x00007f5613ba8650 in std::string::_Rep::_M_clone(std::allocator<char> const&, unsigned long) () from /usr/lib64/libstdc++.so.6
#2  0x00007f5613ba86d4 in std::string::reserve(unsigned long) () from /usr/lib64/libstdc++.so.6
#3  0x00007f5613ba893f in std::string::append(char const*, unsigned long) () from /usr/lib64/libstdc++.so.6
#4  0x0000000000402808 in std::operator+<char, std::char_traits<char>, std::allocator<char> > (
    __lhs="jd-B2", '\000' <repeats 11 times>, "!\000\000\000\000\000\000\000@9@\000\000\000\000\000(I\213\071\375\177\000\000\060I\213\071\375\177\000\000Q\002\000\000\000\000\000\000\"", '\000' <repeats 15 times>, "\001", '\000' <repeats 15 times>, "\377\377\377\377\377\377\377\377\000\000\000\000\000\000\000\000\377\377\377\377\377\377\377\377", '\000' <repeats 88 times>..., __rhs=0x40386a "**")
    at /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/basic_string.h:5917
#5  0x000000000040263f in B2::func1 (this=0x7ffd398b4920, a=0x7ffd398b4930) at string-test.cc:28
#6  0x0000000000401108 in <lambda()>::operator()(void) const (__closure=0x19a5368) at string-test.cc:62
#7  0x0000000000401fde in std::__invoke_impl<void, main()::<lambda()> >(std::__invoke_other, <lambda()> &&) (__f=...) at /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/invoke.h:60
#8  0x0000000000401cb0 in std::__invoke<main()::<lambda()> >(<lambda()> &&) (__fn=...) at /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/invoke.h:95
#9  0x0000000000402392 in std::thread::_Invoker<std::tuple<main()::<lambda()> > >::_M_invoke<0>(std::_Index_tuple<0>) (this=0x19a5368) at /opt/rh/devtoolset-7/root/usr/include/c++/7/thread:234
#10 0x000000000040233f in std::thread::_Invoker<std::tuple<main()::<lambda()> > >::operator()(void) (this=0x19a5368) at /opt/rh/devtoolset-7/root/usr/include/c++/7/thread:243
#11 0x00000000004022fe in std::thread::_State_impl<std::thread::_Invoker<std::tuple<main()::<lambda()> > > >::_M_run(void) (this=0x19a5360) at /opt/rh/devtoolset-7/root/usr/include/c++/7/thread:186
#12 0x000000000040343f in execute_native_thread_routine ()
#13 0x00007f5613df8dd5 in start_thread () from /usr/lib64/libpthread.so.0
#14 0x00007f5613302ead in clone () from /usr/lib64/libc.so.6

比较疑惑的一点是,多线程写 string,为什么不是在写入那一行 core,而是在后面拼接成员变量?

a->name = local_name; // 我理解应该是这一行报 core

std::string key = local_name + "**"; // 实际在操作 local_name 的时候 core,并且看栈,local_name 内存乱了

辛苦各位大佬,有时间的帮忙看看,很是疑惑。 谢谢。

3752 次点击
所在节点    C++
25 条回复
hxndg
2021-02-05 11:37:16 +08:00
@matrixji
不,你这个说法没有解释清楚为什么 local_name + "**"为什么会 core,
每个线程都是在自己的栈上操作,local_name+"**"的结构应该是在本地栈上分配的,即使 assign 的是直接这个地址也不应该 core 才对。
Wirbelwind
2021-02-06 05:03:22 +08:00
@hxndg 上面 a->name = local_name 之后,编译器一定程度上可能会直接使用保存了 a->name 的寄存器(应该是 a)来替代 local_name 的寄存器
hxndg
2021-02-06 11:12:22 +08:00
@Wirbelwind 嗯,我也怀疑是这个,但是没把代码下下来看并不确定
YouLMAO
2021-02-07 19:59:12 +08:00
@blacksmith sse3 很明显异常呀, 因为内存没对齐呀, 不是一个个字节拷贝是一块块拷贝的
vduang
2021-02-09 21:21:02 +08:00
@blacksmith 堆内存只要一乱,程序可能在任何使用堆内存的地方崩溃,崩溃的地方和 bug 的地方可能没有任何关联,这个现象是正常的,也是这样的问题难以排查的原因。

你这段代码的问题在于多个线程中 a->name 被并发赋值,导致 a->name (同时也是 local_name )指向的原来的堆内存被多次释放了,如果这段内存在被释放后又被重新分配出去被写入的话,local_name 指向的就是一堆垃圾了,所以即使你是在读取 localname,并没有修改 localname,程序也会在这里崩溃。

所以这段代码什么时候崩溃在哪崩溃纯看运气。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/751053

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX