Тупик в производитель-потребитель с использованием pthreads

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

//...Local stuff...
if(file){
    while(fgets(line, 256, file)){            
        pthread_mutex_lock(&buffer_mutex);            
        while(data->buffer->buffer_items == data->buffer->buffer_size){               
            pthread_cond_wait(&buffer_full_cv, &buffer_mutex);}
        data->buffer->buffer_items);
        reads++;
        add_to_head(data->buffer, line);
        pthread_cond_broadcast(&buffer_ready_cv);            
        pthread_mutex_unlock(&buffer_mutex);            
    }
    pthread_mutex_lock(&buffer_mutex);
    work = 0;
    pthread_mutex_unlock(&buffer_mutex);
    fclose(file);
}

А это потребитель:

//...Local stuff...
while(1){
    pthread_mutex_lock(&buffer_mutex);        
    while(data->buffer->buffer_items == 0){
        if(work)
            pthread_cond_wait(&buffer_ready_cv, &buffer_mutex);            
        else if(!work && !data->buffer->buffer_items)
            pthread_exit(NULL);
    }
    remove_from_tail(data->buffer, string_to_check);
    data->buffer->buffer_items);
    pthread_cond_signal(&buffer_full_cv);
    pthread_mutex_unlock(&buffer_mutex);

    for(unsigned int i = 0; i < data->num_substrings; i++){    
        cur_occurrence = strstr(string_to_check, data->substrings[i]);
        while(cur_occurrence != NULL){
            pthread_mutex_lock(&buffer_mutex);
            data->occurrences[i]++;
            cur_occurrence++;
            cur_occurrence = strstr(cur_occurrence, data->substrings[i]);
            pthread_mutex_unlock(&buffer_mutex);
        }
    }
}

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

PS.: Я также пробовал использовать pthread_cond_signal вместо широковещательной передачи, но это тоже не сработало.

В любом случае, есть ли что-то, что мне здесь не хватает?


person Fuga    schedule 04.11.2017    source источник
comment
«ожидание потребителя никогда не заканчивается», да. Это какая-то проблема?   -  person Martin James    schedule 04.11.2017
comment
Я имею в виду, ты не можешь просто оставить их? Вы ДОЛЖНЫ прекратить их явно?   -  person Martin James    schedule 04.11.2017
comment
Если я просто оставлю их, когда они закончатся? Редактировать: main должен возвращать значение, которое будет готово только после того, как все потребители закончат работу. Если они никогда не закончатся, когда они вернутся?   -  person Fuga    schedule 05.11.2017


Ответы (1)


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

Технически это не тупик. Это обычная проблема с конфигурациями потоков производитель/потребитель. Существуют различные способы борьбы с этим.

  • Вы можете использовать специальное значение буфера (отдельно от пустого буфера), которое сигнализирует о завершении работы производителя. (Если у вас есть несколько потребителей, это специальное значение должно быть оставлено в буфере.) Такая внутриполосная сигнализация, хотя иногда и удобна для реализации, обычно не является хорошей схемой.
  • Если у вас есть несколько производителей, вам, вероятно, следует объединить буфер со счетчиком числа работающих производителей, по сути, добавив семафор в буфер. Если количество производителей достигает нуля, потребители должны выйти.
  • Поток, порождающий производителей и потребителей, может использовать pthread_cancel после присоединения всех потребителей, так что pthread_cond_wait прерывается. Однако это сложно сделать полностью правильно, и в целом лучше избегать отмены.

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

person Florian Weimer    schedule 04.11.2017
comment
O пытался использовать рабочий флаг, но с более чем двумя потребителями возникла та же проблема. В конце концов буфер становится пустым, но больше нечего загружать, поэтому поток производителя завершается, но в буфере все еще есть данные. Когда буфер окончательно опустеет, потоки, которые все еще работают, застревают в ожидании сигнала, который так и не приходит. По крайней мере, это вывод, который я получил после отладки кода. Также есть только один производитель, и отмена потоков может быть плохой идеей, потому что один из них все еще может обрабатывать некоторые данные - если только он не отменяет только ожидающие потоки. - person Fuga; 04.11.2017
comment
Смотрите последний абзац. Потребителям нужно транслировать сигнал в конце, они выходят. - person Florian Weimer; 04.11.2017
comment
Но потребители не знают, пуст ли буфер из-за того, что производитель перестал читать файл, или он все еще читает. Я думал об этом, но я не мог понять, как сделать эту сигнализацию. - person Fuga; 04.11.2017
comment
Вот почему я написал специальное значение буфера буфера. Пустой буфер не является таким значением. Извините за неясность. Вы всегда можете использовать отдельный флаг или счетчик запущенных производителей. Внутриполосная сигнализация обычно не является хорошей идеей. - person Florian Weimer; 04.11.2017
comment
Но разве это значение не будет таким же, как работа в коде MT? Чтобы было ясно, work — это int, инициализированный как 1. Как только цикл fgets заканчивается, он устанавливается в 0. Тем не менее, кажется, что времени между окончанием цикла и выполнением атрибуции os достаточно, чтобы позволить потребителям войти в состояние ожидания. Я также пробовал трансляцию после того, как файл прочитан производителем, но этого было недостаточно, чтобы освободить все потоки. - person Fuga; 04.11.2017
comment
Вы транслировали от всех потребителей после того, как они наблюдали состояние !work? - person Florian Weimer; 04.11.2017
comment
Я пробовал pthread_cond_broadcast(&buffer_ready_cv) прямо перед тем, как поток существует, но безуспешно. - person Fuga; 04.11.2017
comment
«Внутриполосная передача сигналов обычно не очень хорошая идея», с чего бы это? Это обычная практика. - person Martin James; 04.11.2017
comment
Например, в очень немногих случаях, когда мне приходилось закрывать такой пул, я использовал NULL как «таблетку яда». Каждый потребитель, который получает NULL, помещает NULL обратно в очередь и завершает работу. NULL не нужно освобождать/удалять, поэтому нет проблем с последним умирающим потоком пула, который должен иметь дело с ним. - person Martin James; 04.11.2017