pthreads распараллеливание неверный результат

Я пишу C-программу, используя pthreads. Цель состоит в том, чтобы вычислить кратность заданных чисел, передав их в качестве аргументов. Числа для умножения и количество множителей можно свободно выбирать.

Программа компилируется с gcc -lpthread -Wall -Wextra in.c, исполняемый файл вызывается с ./a.out num amount num amount ...

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

Проблема в том, что программа часто оставляет как минимум один из выходов пустым (0x00). При повторении одного и того же ввода правильный результат появляется редко. Например, с входом ./a.out 10 3 7 5 3 4 выходные данные (здесь сжатые) выглядят так:

Thread 0 result: 10 20 30  or Thread 0 result: 0 0 0    but rarely the Thread 0 result: 10 10 0 
Thread 1 result: 0 0 0 0 0 or Thread 1 result: 0 0 0 0 0   expected    Thread 1 result: 7 14 21 28 35
Thread 2 result: 3 6 9 12  or Thread 2 result: 3 6 9 12     result:    Thread 2 result: 3 6 9 12

Итак, я нашел два обходных пути, но ни один из них не решает проблему. Они включены в код, но закомментированы.

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#define MAX_THREADS 100
int *thr_out[MAX_THREADS]; // global output record
// function to thread
void *threaded_mul(void* arguments[3])
{
  int* out = arguments[0];
  long num = (long)arguments[1];
  long len = (long)arguments[2];
  for(int i=0; i<len; i++)
    out[i]=num*(i+1);
  pthread_exit(NULL);
}

int main(int argc, char* argv[])
{
  int amt_thr = argc/2;       // one thread needs two arguments
  int thr_i_num[amt_thr];     // number to generate multiples
  int thr_o_len[amt_thr];     // how many multiples to generate
  pthread_t thr_id[amt_thr];  // holds thread ids
  long int thr_args[3];       // forms argument for pthread_create call
  printf("%d threads needed\n",amt_thr);
  for(int i=0; i<amt_thr;i++)
  { // calculate how much memory is needed for each thread
    int oi = 2*i+1; // 0 1 2 3 -> 1 3 5 7
    thr_o_len[i] = strtol(argv[oi+1], NULL, 10);
    thr_i_num[i] = strtol(argv[oi], NULL, 10);
    // allocate the memory
    thr_out[i]=calloc(thr_o_len[i], sizeof(int));
  }
  for(int i=0; i<amt_thr; i++)
  { // create threads
    thr_args[0] = (long)thr_out[i]; // address to write output to
    thr_args[1] = thr_i_num[i];     // input 'val' for thread (number to multiply)
    thr_args[2] = thr_o_len[i];     // output length 'len' for thread
    pthread_create(&thr_id[i], NULL, (void*)threaded_mul, &thr_args);
    //for(int i=0; i<32768; i++){}  //  either delay here
    //pthread_join(thr_id[i],NULL); // or wait until the thread finishes
  }
  printf("joining threads\n");
  for(int i=0; i<amt_thr; i++)
    pthread_join(thr_id[i],NULL);

   for(int t=0; t<amt_thr; t++)
  { // printing resuls
    printf("Thread %d result: ",t);
    for(int j=0; j<thr_o_len[t]; j++)
      printf("%d ",thr_out[t][j]);
    putchar('\n');
  }
  for(int i=0; i<amt_thr; i++)
    free(thr_out[i]);
  return 0;
}

Я предполагаю, что после создания потока main продолжается нормально, и поток запускается немедленно (на другом ядре), но с тем же адресным пространством. Мое наблюдение заключается в том, что в большинстве случаев по крайней мере один поток не может получить правильные аргументы, а два или более потока выполняют одни и те же вычисления и записывают в одно и то же место назначения, оставляя, таким образом, другие места назначения вывода нетронутыми.

Как избежать такого поведения?

Редактировать: и, как я понимаю, основываясь на ваших ответах, проблема заключается в том, что до того, как вновь созданный поток сможет прочитать свои аргументы &thr_args из памяти, цикл for //create threads уже записал новые аргументы в thr_args[]. Но аргумент должен быть указателем на память, как того требует pthread_create.

Edit2: я решил эту проблему, записав все входные данные (по 3 в каждом потоке) для всех потоков в память вместо обновления глобальной входной переменной thr_args[] внутри цикла for по причине, указанной в абзаце выше.


person Kitsune    schedule 07.03.2021    source источник
comment
Включите предупреждения компилятора: -Wall -Wextra -pedantic и решите их все.   -  person Marco Bonelli    schedule 07.03.2021
comment
Минимизируйте свой пример. Условия гонки обычно означают, что вы не синхронизируете (мьютекс/условие) доступ к общим данным. Если с задержкой становится лучше, это еще одна подсказка. На самом деле нет смысла отлаживать состояние гонки, пока вы не исправите дефекты, указанные ниже. stdout (т. е. печать) — это общий ресурс.   -  person Allan Wind    schedule 07.03.2021


Ответы (2)


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

Когда вы запускаете свои потоки, вы передаете ссылку на массив (thr_args[]). Это означает, что каждый поток видит один и тот же параметр, который является ячейкой памяти. Вы перезаписываете этот массив в цикле создания потока, поэтому то, что видит любой конкретный поток, зависит от времени / os / #cores. Не совсем случайно, но чертовски близко.

В качестве быстрого хака я изменил вашу программу вокруг pthread_create на:

 void *x = memdup(thr_args, sizeof thr_args);
 pthread_create(&thr_id[i], NULL, threaded_mul, x);

и добавил немного функций выше:

static void *memdup(void *p, size_t n) {
        void *x;
        if ((x = malloc(n))) {
                memcpy(x, p, n);
                return x;
        } else {
                abort();
        }
}

и ваша программа печатает:

Thread 0 result: 10 20 30 
Thread 1 result: 7 14 21 28 35 
Thread 2 result: 3 6 9 12 

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

person mevets    schedule 07.03.2021

  1. Прототип pthread_create:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

но вы проходите в void *threaded_mul(void* arguments[3]). Так должно быть:

void *threaded_mul(void* arg);

а затем вы приводите arg к любому типу, который вам нужен.

  1. В threaded_mul вы передаете подписанный long, когда говорите, что хотите напечатать int:
printf("%d ",num*(i+1));

Так должно быть:

printf("%ld ",num*(i+1));
  1. В main у вас есть:
thr_args[0] = thr_out[i];

где lhs — это int, а правая часть — указатель на int. Это, вероятно, не хотите, вы хотите.

person Allan Wind    schedule 07.03.2021