2015-09-14 33 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

相關問題