Почему pthread_join не работает на последней итерации (что приводит к ошибке сегментации)?

Новичок в многопоточности здесь. Ровно на 5-й итерации (т. Е. При выполнении pthread_join (threadID [4], NULL) моя программа терпит неудачу из-за ошибки сегментации.

Я создаю несколько потоков для добавления / вычитания 1 из переменной счетчика для изучения условий гонки. Все работает плавно, пока я не попробую 5 потоков или больше. Ровно на последней итерации pthread_join (threadID [4], NULL) происходит сбой, и я не могу определить почему. Я уверен, что проблема здесь, поскольку я использовал операторы printf, чтобы увидеть, где он достигает, прежде чем сбой.

#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <time.h>

int opt_threads;
int opt_iterations;
long nThreads;
long nIterations;
int opt_yield;
long long counter;

void add(long long *pointer, long long value) {
  long long sum = *pointer + value;
  if (opt_yield)
    sched_yield();
  *pointer = sum;
}

void *thread_worker(void * arg) {
  long i;
  for (i=0; i<nIterations; i++) {
    add(&counter, 1);
    add(&counter, -1);
  }

  return arg;
}

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

  pthread_t *threadID = malloc(nThreads * sizeof(pthread_t));
  if (threadID == NULL) {
    fprintf(stderr, "Thread memory allocation failed\n");
    exit(1);
  }

  static struct option long_options[] =
    {
      {"threads", required_argument, 0, 't'},
      {"iterations", required_argument, 0, 'i'},
      {"yield", no_argument, 0, 'y'},
      {0,0,0,0}
    };

  while (1) {
    c = getopt_long(argc, argv, "", long_options, NULL);
    if (c==-1) break;

    switch(c) {
    case 't':
      opt_threads = 1;
      nThreads = atoi(optarg);
      break;
    case 'i':
      opt_iterations = 1;
      nIterations = atoi(optarg);
      break;

    case 'y':
      opt_yield = 1;
      break;

    default:
      fprintf(stderr, "Bad argument!\n");
      exit(1);
    }
   }

counter = 0;
struct timespec start, finish;
int i;

//start clock
clock_gettime(CLOCK_MONOTONIC, &start);

//create
for (i=0; i < nThreads; i++) {
  pthread_create(&threadID[i], NULL, &thread_worker, NULL);
  printf("Created thread[%ld]\n", i);
}

//wait (join)
  /*for (i=0; i < nThreads; i++) {
    printf("Now i is %ld\n", i);
    if (pthread_join(threadID[i], NULL) != 0)
      fprintf(stdout,"ERRRRROOOORRRRRRRR\n");

  }*/

  pthread_join(threadID[0], NULL);
  pthread_join(threadID[1], NULL);
  pthread_join(threadID[2], NULL);
  pthread_join(threadID[3], NULL);
  pthread_join(threadID[4], NULL);

  printf("about to end clock\n");
  //finish clock
  clock_gettime(CLOCK_MONOTONIC, &finish);

  printf("finished clock\n");

  long seconds = finish.tv_sec - start.tv_sec;
  long ns = finish.tv_nsec - start.tv_nsec;
  long runTime = (seconds + ns) * 1000000000L;
  long nOperations = nThreads * nIterations * 2;
  long avgOperations = runTime / nOperations;
  long run_time = 1000000000L * (finish.tv_sec - start.tv_sec) + finish.tv_nsec - start.tv_nsec;

  //Print
  if (opt_yield == 0)
    fprintf(stdout, "add-none, %ld, %ld, %lld, %ld, %lld, %lld\n", nThreads, nIterations, nOperations, run_time, run_time/nOperations, counter);

  else if (opt_yield == 1)
    fprintf(stdout, "add-yield-none, %ld, %ld, %lld, %ld, %lld, %lld\n",nThreads, nIterations, nOperations, run_time, run_time/nOperations, counter);

exit(0);
}

Я ожидаю, что программа сможет правильно дождаться 5-го потока, но она не работает из-за ошибки сегментации.


person bantz    schedule 04.08.2019    source источник
comment
как вы определили thread_worker? вы можете опубликовать полный код? Я подозреваю, что что-то перезаписывает память, и в пятый раз наконец достигается какая-то критическая переменная, такая как указатель или индекс массива.   -  person Skaperen    schedule 05.08.2019
comment
Конечно. См. Обновленную версию раздела кода.   -  person bantz    schedule 05.08.2019
comment
Итак, стандартный перечень вопросов, касающихся того, что вы не включили, и всех вопросов, на которые нужно ответить с помощью минимальный воспроизводимый пример. Мы понятия не имеем, что такое nThreads (помимо того, что вы сказали, а не того, что вы показали). Мы понятия не имеем, что такое threadID. Это вероятно массив, но мы не знаем. Мы понятия не имеем, что thread_worker делает.   -  person WhozCraig    schedule 05.08.2019
comment
@WhozCraig вы можете сделать вывод, что threadID - это идентификатор каждого созданного потока. nThreads = количество потоков. Пожалуйста, будьте более приветливы и уважительны по отношению к новым членам этого сообщества.   -  person bantz    schedule 05.08.2019
comment
относительно: pthread_join(threadID[0], NULL); pthread_join(threadID[1], NULL); pthread_join(threadID[2], NULL); pthread_join(threadID[3], NULL); pthread_join(threadID[4], NULL); Это будет работать, только если параметр командной строки для потоков равен 5   -  person user3629249    schedule 05.08.2019
comment
Да, я добавил эти строки, чтобы проверить, в каком потоке произошел сбой. Обычно я бы использовал там цикл.   -  person bantz    schedule 05.08.2019


Ответы (2)


Ваша main функция запускается:

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

    pthread_t *threadID = malloc(nThreads * sizeof(pthread_t));

Поскольку nThreads - глобальная переменная без явного инициализатора, ее значение равно нулю - вы не выделили память, которую можно законно использовать. Доступ к этой памяти приводит к неопределенному поведению.

Отложите это выделение памяти до тех пор, пока не узнаете, сколько потоков вам понадобится.

Неопределенное поведение означает, что может произойти все, что угодно, в том числе кажется работающим до тех пор, пока оно больше не перестанет работать.

Вам также необходимо переписать код присоединения к потоку как цикл, чтобы он соответствовал циклу, который создает потоки.

person Jonathan Leffler    schedule 04.08.2019

Есть МНОГО проблем с опубликованным кодом.

Вместо того, чтобы перечислять их все, я просто представлю рабочую версию кода. Вы можете выполнить сравнение, чтобы увидеть изменения.

Примечание. Я предоставил некоторые значения параметров по умолчанию, если пользователь не указал параметры командной строки. В противном случае деление на nIterations приведет к исключению "деление на 0".

Я использовал функцию массива переменной длины языка C для объявления массива pThread_t вместо использования динамического распределения памяти.

В общем, включение файлов заголовков, содержимое которых не используется, является плохой практикой программирования.

Обратите внимание, что в функции: add() это 1) обновление значения общего счетчика не защищено pthread_mutex_t, поэтому счетчик может / будет поврежден другими потоками. Также вызов sched_yield() между обновлением локальной переменной sum и обновлением глобальной counter означает, что результаты других потоков могут / будут уничтожены.

#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
//#include <unistd.h>
#include <pthread.h>
//#include <stdint.h>
//#include <errno.h>
//#include <string.h>
#include <getopt.h>
#include <time.h>

int opt_threads;
int opt_iterations;
long nThreads;
long nIterations;
int opt_yield;
long long counter;

void add(long long *pointer, long long value) 
{
    long long sum = *pointer + value;
    if (opt_yield)
    {
        sched_yield();
    }
    *pointer = sum;
}

void *thread_worker(void * arg) 
{
    void(arg);
    //long i;
    for ( long i=0; i<nIterations; i++) 
    {
        add(&counter, 1);
        add(&counter, -1);
    }

    //return arg;
    pthread_exit( NULL );
}

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

    static struct option long_options[] =
    {
        {"threads", required_argument, 0, 't'},
        {"iterations", required_argument, 0, 'i'},
        {"yield", no_argument, 0, 'y'},
        {0,0,0,0}
    };

    while ( (c = getopt_long(argc, argv, "", long_options, NULL)) != -1 )
    {
        switch(c) 
        {
            case 't':
                opt_threads = 1;
                nThreads = atoi(optarg);
                break;

            case 'i':
                opt_iterations = 1;
                nIterations = atoi(optarg);
                break;

            case 'y':
                opt_yield = 1;
                break;

            default:
                fprintf(stderr, "Bad argument!\n");
                exit( EXIT_FAILURE );
                break;
        }
    }

    if( nIterations == 0 )
    {
        nIterations = 1;
        opt_iterations = 1;
    }

    if( nThreads == 0 )
    {
        nThreads = 5;
        opt_threads = 1;
    }

    pthread_t threadID[ nThreads ];

    counter = 0;
    struct timespec start, finish;
    //int i;

    //start clock
    clock_gettime(CLOCK_MONOTONIC, &start);

    //create
    for ( int i=0; i < nThreads; i++) 
    {
        if( pthread_create(&threadID[i], NULL, &thread_worker, NULL) < 0 )
        {
            perror( "pthread_create failed" );
            exit( EXIT_FAILURE );
        }
        printf("Created thread[%d]\n", i);
    }

    for( int i = 0; i<nThreads; i++ )
    {
        pthread_join(threadID[i], NULL);
    }

    printf("about to end clock\n");
    //finish clock
    clock_gettime(CLOCK_MONOTONIC, &finish);

    printf("finished clock\n");

    //long seconds = finish.tv_sec - start.tv_sec;
    //long ns = finish.tv_nsec - start.tv_nsec;
    //long run_Time = seconds * 1000000000L + ns;
    long nOperations = nThreads * nIterations * 2;
    //long avgOperations = run_Time / nOperations;
    long run_duration = 1000000000L * (finish.tv_sec - start.tv_sec) + finish.tv_nsec - start.tv_nsec;

    //Print
    if (opt_yield == 0)
    fprintf(stdout, "add-none, %ld, %ld, %ld, %ld, %ld, %lld\n", 
        nThreads, 
        nIterations, 
        nOperations, 
        run_duration, 
        run_duration/nOperations, 
        counter);

    else 
    {
        fprintf(stdout, "add-yield-none, %ld, %ld, %ld, %ld, %ld, %lld\n",
            nThreads, 
            nIterations, 
            nOperations, 
            run_duration, 
            run_duration/nOperations, 
            counter);
    }

    exit(0);
}

Приведенный выше код без параметров командной строки дает следующий результат.

Created thread[0]
Created thread[1]
Created thread[2]
Created thread[3]
Created thread[4]
about to end clock
finished clock
add-none, 5, 1, 10, 3544641, 354464, 0
person user3629249    schedule 05.08.2019