Я получаю гонку данных при вызове 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/ Особенно ошибка в примере.
Я до сих пор не понимаю, что происходит на самом деле.
Вы пытались добавить '-pthread' в правило компиляции makefile? – alk
Да, но я удалил его. Это не имело никакого эффекта. Я думаю, что это необходимо только для фазы связывания. – MBanks