2015-09-14 2 views
2

Я получаю гонку данных при вызове pthread_create() рекурсивно. Я не знаю, вызвала ли рекурсия проблему, , но гонка, кажется, никогда не встречается на первой итерации, главным образом на второй и редко на третьей.Почему этот рекурсивный вызов pthread_create приводит к гонке данных?

При использовании libgc появляются признаки повреждения памяти, такие как ошибка сегментации, которые совпадают с расходом данных.

Следующая программа представляет собой минимальный пример, иллюстрирующий проблему. Я не использую libgc в примере, поскольку тема этого вопроса касается только гонки данных.

Гонка данных видна при запуске Valgrind с помощью инструмента Helgrind. Имеются незначительные изменения в сообщениях о проблемах, в том числе иногда нет проблем.

Я использую 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 ; 
} 

Вот «Makefile»:

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 

Вот самый общий вывод Хелгринда. Обратите внимание на то, что линии только с одной цифрой, 1, 2 и 3, выводятся из программы и не Хелгринда:

$ 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' в правило компиляции makefile? – alk

+0

Да, но я удалил его. Это не имело никакого эффекта. Я думаю, что это необходимо только для фазы связывания. – MBanks

ответ

1

Выход helgrind не соответствует вашему источнику. Согласно helgrind, в строке 25 есть звонок pthread_create, но все, что я вижу, - exit(-1). Я предполагаю, что вы забыли добавить строку в начале источника.

Это, как говорится, я не могу воспроизвести выход helgrind вообще. Я запустил вашу программу в цикле while, надеясь получить ту же ошибку, но нада. Это неприятная вещь о гонках - вы никогда не знаете, когда они происходят, и их трудно отследить.

Тогда есть еще одна вещь: res_thread_freeres вызывается всякий раз, когда информация о состоянии преобразователя (DNS) будет освобождена. На самом деле, он называется, даже не будучи проверенным. И _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_detach(*thread) ; на pthread_join(*thread,NULL);. Это обеспечит, чтобы ребенок заканчивался перед родителем и, следовательно, не было ошибок seg.

+0

Это удаляет гонку данных, но wth pthread_join() вызывает ожидание завершения только что созданного потока, существенно устраняя параллельность и делая программу последовательной? – MBanks

+0

Вы не устраняете параллелизм, вы только гарантируете, что ребенок заканчивается перед родителем, вы все равно печатаете список параллельно. Таким образом, это параллельное выполнение и последовательное завершение. На самом деле, чтобы обеспечить лучший параллелизм, я бы предложил вам переписать свою функцию. Вместо создания pthreads в функции 'call()' создайте ее в функции 'print_list_start_routine()'. –

+0

Я понимаю. Я подумаю об этом. Это может быть здорово! – MBanks

Смежные вопросы