2015-09-14 31 views
2

递归调用pthread_create()时,我正在获取数据竞争。 我不知道递归是否会导致问题, 但在第一次迭代中似乎永远不会发生竞争,大部分是在第二次迭代,很少在第三次。为什么此递归pthread_create调用导致数据竞争?

使用libgc时,会出现与数据争用一致的内存损坏症状,如分段故障。

以下程序是说明问题的最简单示例。 我没有在示例中使用libgc,因为只有数据竞赛才是这个问题的主题。

使用Helgrind工具运行Valgrind时可以看到数据竞争。 报告的问题有轻微的变化,包括有时没有问题。

我正在运行Linux Mint 17.2。 gcc的版本是(Ubuntu 4.8.4-2ubuntu1〜14.04)4.8.4。

以下示例'main.c'重现了此问题。它遍历链接列表,在单独的线程打印每个元素值:

#include <stdlib.h> 
#include <stdio.h> 
#include <pthread.h> 


typedef struct List { 
    int head ; 
    struct List* tail ; 
} List ; 

// create a list element with an integer head and a tail 
List* new_list(int head, List* tail) { 
    List* l = (List*)malloc(sizeof(List)) ; 
    l->head = head ; 
    l->tail = tail ; 
    return l ; 
} 


// create a thread and start it 
void call(void* (*start_routine)(void* arg), void* arg) { 
    pthread_t* thread = (pthread_t*)malloc(sizeof(pthread_t)) ; 

    if (pthread_create(thread, NULL, start_routine, arg)) { 
    exit(-1) ; 
    } 

    pthread_detach(*thread) ; 
    return ; 
} 


void print_list(List* l) ; 

// start routine for thread 
void* print_list_start_routine(void* arg) { 

    // verify that the list is not empty (= NULL) 
    // print its head 
    // print the rest of it in a new thread 
    if (arg) { 

    List* l = (List*)arg ; 

    printf("%d\n", l->head) ; 

    print_list(l->tail) ; 

    } 

    return NULL ; 
} 

// print elements of a list with one thread for each element printed 
// threads are created recursively 
void print_list(List* l) { 
    call(print_list_start_routine, (void*)l) ; 
} 


int main(int argc, const char* argv[]) { 

    List* l = new_list(1, new_list(2, new_list(3, NULL))) ; 

    print_list(l) ; 

    // wait for all threads to finnish 
    pthread_exit(NULL) ; 

    return 0 ; 
} 

这里是“生成文件”:

CC=gcc 

a.out: main.o 
    $(CC) -pthread main.o 

main.o: main.c 
    $(CC) -c -g -O0 -std=gnu99 -Wall main.c 

clean: 
    rm *.o a.out 

这里是Helgrind最常见的输出。请注意,只有一个单一的数字,1,2和3中的线是该程序的输出,而不是Helgrind:

$ valgrind --tool=helgrind ./a.out 
==13438== Helgrind, a thread error detector 
==13438== Copyright (C) 2007-2013, and GNU GPL'd, by OpenWorks LLP et al. 
==13438== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info 
==13438== Command: ./a.out 
==13438== 
1 
2 
==13438== ---Thread-Announcement------------------------------------------ 
==13438== 
==13438== Thread #3 was created 
==13438== at 0x515543E: clone (clone.S:74) 
==13438== by 0x4E44199: do_clone.constprop.3 (createthread.c:75) 
==13438== by 0x4E458BA: [email protected]@GLIBC_2.2.5 (createthread.c:245) 
==13438== by 0x4C30C90: ??? (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so) 
==13438== by 0x4007EB: call (main.c:25) 
==13438== by 0x400871: print_list (main.c:58) 
==13438== by 0x40084D: print_list_start_routine (main.c:48) 
==13438== by 0x4C30E26: ??? (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so) 
==13438== by 0x4E45181: start_thread (pthread_create.c:312) 
==13438== by 0x515547C: clone (clone.S:111) 
==13438== 
==13438== ---Thread-Announcement------------------------------------------ 
==13438== 
==13438== Thread #2 was created 
==13438== at 0x515543E: clone (clone.S:74) 
==13438== by 0x4E44199: do_clone.constprop.3 (createthread.c:75) 
==13438== by 0x4E458BA: [email protected]@GLIBC_2.2.5 (createthread.c:245) 
==13438== by 0x4C30C90: ??? (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so) 
==13438== by 0x4007EB: call (main.c:25) 
==13438== by 0x400871: print_list (main.c:58) 
==13438== by 0x4008BB: main (main.c:66) 
==13438== 
==13438== ---------------------------------------------------------------- 
==13438== 
==13438== Possible data race during write of size 1 at 0x602065F by thread #3 
==13438== Locks held: none 
==13438== at 0x4C368F5: mempcpy (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so) 
==13438== by 0x4012CD6: _dl_allocate_tls_init (dl-tls.c:436) 
==13438== by 0x4E45715: [email protected]@GLIBC_2.2.5 (allocatestack.c:252) 
==13438== by 0x4C30C90: ??? (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so) 
==13438== by 0x4007EB: call (main.c:25) 
==13438== by 0x400871: print_list (main.c:58) 
==13438== by 0x40084D: print_list_start_routine (main.c:48) 
==13438== by 0x4C30E26: ??? (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so) 
==13438== by 0x4E45181: start_thread (pthread_create.c:312) 
==13438== by 0x515547C: clone (clone.S:111) 
==13438== 
==13438== This conflicts with a previous read of size 1 by thread #2 
==13438== Locks held: none 
==13438== at 0x51C10B1: res_thread_freeres (in /lib/x86_64-linux-gnu/libc-2.19.so) 
==13438== by 0x51C1061: __libc_thread_freeres (in /lib/x86_64-linux-gnu/libc-2.19.so) 
==13438== by 0x4E45199: start_thread (pthread_create.c:329) 
==13438== by 0x515547C: clone (clone.S:111) 
==13438== 
3 
==13438== 
==13438== For counts of detected and suppressed errors, rerun with: -v 
==13438== Use --history-level=approx or =none to gain increased speed, at 
==13438== the cost of reduced accuracy of conflicting-access information 
==13438== ERROR SUMMARY: 8 errors from 1 contexts (suppressed: 56 from 48) 

正如普加Nilangekar提到,更换pthread_detach()与在pthread_join()删除了比赛。然而,分离线程是一个要求,因此我们的目标是让干净地分离线程。换句话说,在消除竞争的同时保持pthread_detach()。

线程之间似乎存在一些意外共享。 意外共享可能与此处讨论的内容有关:http://www.domaigne.com/blog/computing/joinable-and-detached-threads/ 特别是该示例中的错误。

我还是不明白到底发生了什么。

+0

您是否尝试在生成文件的编译规则中添加'-pthread'? – alk

+0

是的,但我删除它。它没有效果。我认为这只是连接阶段的必要条件。 – MBanks

回答

1

helgrind的输出与您的来源不匹配。根据helgrind,在第25行有一个pthread_create呼叫,但我看到的全部是exit(-1)。我假设你忘记在源代码的开头添加一行。

这就是说,我根本无法重现helgrind的输出。我已经在while循环中运行你的程序,希望能得到同样的错误,但是nada。这是关于种族的肮脏事情 - 你永远不知道它们什么时候发生,而且很难追踪。

然后还有一件事:每当解析器状态信息(DNS)将被释放时,都会调用res_thread_freeres。实际上,它甚至没有被检查就被调用。 _dl_allocate_tls_init用于线程本地存储(TLS),并确保某些资源和元数据(自定义堆栈,清理信息等)。)在您的函数被赋予对线程的控制之前被分配/存储。

这表明在创建新线程和杀死旧线程之间存在竞争。由于你分离你的线程,所以在子结束之前父线程可能会死掉。在这种情况下,同步退出的线程(Pooja Nilangekar指出可以通过加入它们来完成)可以解决此问题,因为pthread_join会停滞,直到线程完成,从而同步子/父释放。

如果你仍然想要进行并行处理,你可以做的是你自己照顾内存。具体见pthread_attr_setstack。由于我无法重现错误,所以我还没有确定这是否真的有效。 此外,这种方法要求您知道您将拥有的线程数量。如果您尝试重新分配线程当前使用的内存,则您正在玩火。

+0

我在源代码中删除了一些线条,但没有想到。据我所知,我没有使用TLS。有没有办法验证这一点?另外,我认为没有使用DNS。 – MBanks

+0

正如我刚刚所说 - res_thread_freeres调用,检查您是否使用DNS在该函数内。即使该检查将在功能之外 - 检查仍有待完成。如果该检查是读取一个字节,则存在竞争条件。并且该实例中的TLS指的是即使对于线程也基本上不可见的数据,因为在线程分配中总会有一些开销。但它存储在与线程的整个堆栈帧相同的内存块中。 – Dachschaden

+0

我认为带pthread_attr_setstack()的解决方案是正确的选择。我还没有尝试过。 – MBanks

1

pthread_join(*thread,NULL);替换行pthread_detach(*thread) ;。这将确保孩子在父母之前终止,因此没有seg故障。

+0

这可以消除数据竞争,但不会导致pthread_join()等待新创建的线程完成,从本质上消除并行性并使程序顺序? – MBanks

+0

您不是在删除并行性,只确保子级在父级之前终止,而是仍在平行打印列表。所以它是并行执行和顺序终止。其实为了确保更好的并行性,我建议你重写你的函数。不要在'call()'函数中创建pthread,而是在'print_list_start_routine()'函数中创建它。 –

+0

我明白了。我会考虑一下。这可能太棒了! – MBanks

相关问题