pthreads - тупик на pthread_cond_wait - PullRequest
0 голосов
/ 26 апреля 2018

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

#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
struct thefiles * mailbox;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t flag =  PTHREAD_COND_INITIALIZER;
struct thefiles {
  char    *name;
  int linecount;
};
void *count_words(void *a) {
  char    *buffer;
  int          fd;
  size_t    total;
  int   count = 0;
  int    size = 0;
  struct thefiles *fls = malloc(sizeof(*fls));

  fd = open(a, O_RDONLY);
  size   = lseek(fd, 0, SEEK_END);
  buffer =  malloc(size);
  lseek(fd, 0, SEEK_SET);
  read(fd, buffer, size);
  while (*buffer) {
    if ( *buffer == '\n' )
      count++;
    buffer++;
        }
  fls->name = a;
  fls->linecount = count;
  pthread_mutex_lock(&lock);
  if (mailbox != NULL)
    pthread_cond_wait(&flag, &lock);
  mailbox = fls;
  pthread_cond_signal(&flag);
  pthread_mutex_unlock(&lock);
}

int main(int argc, char *argv[argc]) {
  int                i;
  int        total = 0;
  pthread_t   *threads;
  struct thefiles *fls;
  threads = malloc(sizeof(pthread_t)*(--argc));
  for (i = 0; i < argc; i++) {
    pthread_create(&threads[i], NULL, count_words, argv[i+1]);
  }
  pthread_mutex_lock(&lock);
  while (argc) {
    pthread_cond_wait(&flag, &lock);
    total += mailbox->linecount;
    free(mailbox);
    mailbox = NULL;
    pthread_cond_signal(&flag);
    argc--;
  }
  printf("%d\n", total);
  for (i = 0; i < argc; i++) {
    pthread_join(threads[i], NULL);
  }
}

Ответы [ 2 ]

0 голосов
/ 27 апреля 2018

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

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>

struct thefiles {
  char *name;
  int linecount;
  int fo_errno;
};

void *count_lines(void *args)
{
    struct thefiles *data = args;

    FILE *fp = fopen(data->name, "r");

    if(fp == NULL)
    {
        data->linecount = -1; // signaling error
        data->fo_errno = errno;
        pthread_exit(0);
    }

    data->linecount = 0;

    char line[1024];
    char *nl;
    while(fgets(line, sizeof line, fp))
    {
        nl = strchr(line, '\n');

        if(nl)
            data->linecount++;
    }

    // last line of file did not end in newline
    if(nl == NULL)
        data->linecount++;

    fclose(fp);

    pthread_exit(0);
}

int main(int argc, char **argv)
{
    if(argc < 2)
    {
        fprintf(stderr, "usage: %s file [file ...]\n", argv[0]);
        return 1;
    }

    struct thefiles *fls = calloc(argc - 1, sizeof *fls);
    if(fls == NULL)
    {
        fprintf(stderr, "Not enough memory\n");
        return 1;
    }

    pthread_t *ths = calloc(argc - 1, sizeof *ths);

    if(ths == NULL)
    {
        free(fls);
        fprintf(stderr, "Not enough memory\n");
        return 1;
    }

    for(int i = 0; i < argc - 1; ++i)
    {
        fls[i].name = argv[i+1];

        pthread_create(ths + i, NULL, count_lines, fls + i);
    }

    int total = 0;
    for(int i = 0; i < argc - 1; ++i)
    {
        pthread_join(ths[i], NULL);
        if(fls[i].linecount == -1)
        {
            fprintf(stderr, "%s: %s: %s\n", argv[0], fls[i].name, strerror(fls[i].fo_errno));
            continue;
        }

        printf("%4d %s\n", fls[i].linecount, fls[i].name);
        total += fls[i].linecount;
    }

    printf("%4d total\n", total);

    free(ths);
    free(fls);

    return 0;
}

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

$ ./b /etc/fstab a.c b.c aa
  46 /etc/fstab
 124 a.c
  98 b.c
./b: aa: No such file or directory
 268 total

$ wc -l /etc/fstab a.c b.c aa
  46 /etc/fstab
 124 a.c
  98 b.c
wc: aa: No such file or directory
 268 total

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

0 голосов
/ 26 апреля 2018

У вас есть как минимум следующие проблемы:

  • В вашем цикле в main() вы не проверяете, является ли mailbox NULL, когда возвращается pthread_cond_wait(). Функция pthread_cond_wait() может вернуться в любое время, даже если pthread_cond_signal() не был вызван, поэтому необходимо учитывать случай, когда это происходит.

  • Аналогично, в count_words() вы не проверяете, стало ли mailbox НЕДЕЙСТВИТЕЛЬНЫМ. pthread_cond_wait() всегда должен вызываться в цикле while, например:

      while (!condition) {
          pthread_cond_wait()
      }
    

Также я рекомендую проверить ваши коды ошибок - даже если вы просто распечатываете ошибку и завершаете работу, это гораздо более познавательно, чем смотреть на программу, которая ничего не делает:)

...