Я написал код для создания некоторых потоков и каждый раз, когда один из потоков заканчивается, новый поток создается для замены его. Поскольку я не смог создать очень большое количество потоков (> 450) использующий pthreads, я использовал системный вызов клона вместо этого. (Обратите внимание на то, что я знаю об импликации наличия такого огромного количества потоков, но эта программа предназначена, чтобы только подчеркнуть систему).
Поскольку клон () требует, чтобы стековое пространство для дочернего потока было указано как параметр, я malloc необходимый блок стекового пространства для каждого потока и освободило его, когда поток заканчивается. Когда поток заканчивается, я отправляю сигнал в родителя для уведомления его относительно того же.
Код дан ниже:
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#define NUM_THREADS 5
unsigned long long total_count=0;
int num_threads = NUM_THREADS;
static int thread_pids[NUM_THREADS];
static void *thread_stacks[NUM_THREADS];
int ppid;
int worker() {
int i;
union sigval s={0};
for(i=0;i!=99999999;i++);
if(sigqueue(ppid, SIGUSR1, s)!=0)
fprintf(stderr, "ERROR sigqueue");
fprintf(stderr, "Child [%d] done\n", getpid());
return 0;
}
void sigint_handler(int signal) {
char fname[35]="";
FILE *fp;
int ch;
if(signal == SIGINT) {
fprintf(stderr, "Caught SIGINT\n");
sprintf(fname, "/proc/%d/status", getpid());
fp = fopen(fname,"r");
while((ch=fgetc(fp))!=EOF)
fprintf(stderr, "%c", (char)ch);
fclose(fp);
fprintf(stderr, "No. of threads created so far = %llu\n", total_count);
exit(0);
} else
fprintf(stderr, "Unhandled signal (%d) received\n", signal);
}
int main(int argc, char *argv[]) {
int rc, i; long t;
void *chld_stack, *chld_stack2;
siginfo_t siginfo;
sigset_t sigset, oldsigset;
if(argc>1) {
num_threads = atoi(argv[1]);
if(num_threads<1) {
fprintf(stderr, "Number of threads must be >0\n");
return -1;
}
}
signal(SIGINT, sigint_handler);
/* Block SIGUSR1 */
sigemptyset(&sigset);
sigaddset(&sigset, SIGUSR1);
if(sigprocmask(SIG_BLOCK, &sigset, &oldsigset)==-1)
fprintf(stderr, "ERROR: cannot block SIGUSR1 \"%s\"\n", strerror(errno));
printf("Number of threads = %d\n", num_threads);
ppid = getpid();
for(t=0,i=0;t<num_threads;t++,i++) {
chld_stack = (void *) malloc(148*512);
chld_stack2 = ((char *)chld_stack + 148*512 - 1);
if(chld_stack == NULL) {
fprintf(stderr, "ERROR[%ld]: malloc for stack-space failed\n", t);
break;
}
rc = clone(worker, chld_stack2, CLONE_VM|CLONE_FS|CLONE_FILES, NULL);
if(rc == -1) {
fprintf(stderr, "ERROR[%ld]: return code from pthread_create() is %d\n", t, errno);
break;
}
thread_pids[i]=rc;
thread_stacks[i]=chld_stack;
fprintf(stderr, " [index:%d] = [pid:%d] ; [stack:0x%p]\n", i, thread_pids[i], thread_stacks[i]);
total_count++;
}
sigemptyset(&sigset);
sigaddset(&sigset, SIGUSR1);
while(1) {
fprintf(stderr, "Waiting for signal from childs\n");
if(sigwaitinfo(&sigset, &siginfo) == -1)
fprintf(stderr, "- ERROR returned by sigwaitinfo : \"%s\"\n", strerror(errno));
fprintf(stderr, "Got some signal from pid:%d\n", siginfo.si_pid);
/* A child finished, free the stack area allocated for it */
for(i=0;i<NUM_THREADS;i++) {
fprintf(stderr, " [index:%d] = [pid:%d] ; [stack:%p]\n", i, thread_pids[i], thread_stacks[i]);
if(thread_pids[i]==siginfo.si_pid) {
free(thread_stacks[i]);
thread_stacks[i]=NULL;
break;
}
}
fprintf(stderr, "Search for child ended with i=%d\n",i);
if(i==NUM_THREADS)
continue;
/* Create a new thread in its place */
chld_stack = (void *) malloc(148*512);
chld_stack2 = ((char *)chld_stack + 148*512 - 1);
if(chld_stack == NULL) {
fprintf(stderr, "ERROR[%ld]: malloc for stack-space failed\n", t);
break;
}
rc = clone(worker, chld_stack2, CLONE_VM|CLONE_FS|CLONE_FILES, NULL);
if(rc == -1) {
fprintf(stderr, "ERROR[%ld]: return code from clone() is %d\n", t, errno);
break;
}
thread_pids[i]=rc;
thread_stacks[i]=chld_stack;
total_count++;
}
fprintf(stderr, "Broke out of infinite loop. [total_count=%llu] [i=%d]\n",total_count, i);
return 0;
}
Я использовал несколько массивов для отслеживания pid дочерних процессов и базовый адрес области стека (для освобождения его).
Когда я запускаю эту программу, она завершается после когда-то. Выполнение с gdb говорит мне, что один из потока получает SIGSEGV (отказ сегментации). Но это не делает дает мне любое местоположение, вывод подобен следующему:
Program received signal SIGSEGV, Segmentation fault.
[Switching to LWP 15864]
0x00000000 in ?? ()
Я пытался выполнить его под valgrind со следующей командной строкой:
valgrind --tool=memcheck --leak-check=yes --show-reachable=yes -v --num-callers=20 --track-fds=yes ./a.out
Но это продолжает бежать без любых проблем под valgrind.
Я озадачен относительно того, как отладить эту программу. Я чувствовал, что это могло бы быть некоторым переполнением стека, или что-то кроме увеличения размера стека (до 74 КБ) не сделало решил проблему.
Мой единственный запрос - почему и то, где отказ сегментации или как отладить эту программу.
нашел реальный вопрос.
Когда работник нити сигнализирует о родительском процессе с помощью SIGQUEUE (), родитель иногда сразу же получает контроль и освобождает стека до того, как ребенок выполняет оператор возврата. Когда та же дочерняя нить использует оператор возврата, он вызывает ошибку сегментации, поскольку стек поврежден.
Решить это я заменил
exit(0)
вместо
return 0;
Кажется, я нашел ответ
Шаг 1
Заменить это:
static int thread_pids[NUM_THREADS];
static void *thread_stacks[NUM_THREADS];
На это:
static int *thread_pids;
static void **thread_stacks;
Шаг 2
Добавить это в основную функцию (после проверки аргументов):
thread_pids = malloc(sizeof(int) * num_threads);
thread_stacks = malloc(sizeof(void *) * num_threads);
Шаг 3
Заменить это:
chld_stack2 = ((char *)chld_stack + 148*512 - 1);
На это:
chld_stack2 = ((char *)chld_stack + 148*512);
В обоих местах вы используете это.
Я не знаю, действительно ли это ваша проблема, но после тестирования я не получил никаких ошибок сегментации. Btw я получил ошибки сегментации только при использовании более 5 потоков.
Надеюсь, я помог!
edit: test with 1000 threads and runs perfectly
edit2: Объяснение, почему статическое выделение thread_pids и thread_stacks приводит к ошибке.
Лучше всего это сделать на примере.
Предположим, num_threads = 10;
Проблема возникает в следующем коде:
for(t=0,i=0;t<num_threads;t++,i++) {
...
thread_pids[i]=rc;
thread_stacks[i]=chld_stack;
...
}
Здесь пытаются получить доступ к памяти, которая вам не принадлежит (0 <= i <= 9, но оба массива имеют размер 5). Это может привести как к ошибке сегментации, так и к повреждению данных. Повреждение данных может произойти, если оба массива будут выделены один за другим, что приведет к записи в другой массив. Сегментация может произойти, если запись производится в память, которая не была выделена (статически или динамически).
Возможно, вам повезёт и ошибки вообще не возникнут, но код, безусловно, не безопасен.
О не выровненном указателе: Думаю, мне не нужно объяснять больше, чем в комментарии.