Условное ожидание с pthreads

Кажется, я захожу в возможный тупик с условной переменной pthreads.

Вот код

thread function(){
    for (condition){
        do work
        /* should the thread continue? */
        if (exit == 1){
            break; /* exit for */
        } 
     } /* end for */

pthread_mutex_lock(&mtxExit);
exit = 0;
pthread_cond_signal(&condVar);
pthread_mutex_unlock(&mtxExit);
}

Основная функция заключается в следующем:

function main(){
    if (thread is still active){
          pthread_mutex_lock(&mtxExit);
          exit = 1;
          pthread_mutex_unlock(&mtxExit);
          } /* end if */
    while (exit == 1){
       pthread_mutex_lock(&mtxExit);
       /* check again */
       if (exit == 1)
           pthread_cond_wait(&condVar, &mtxExit);
       pthread_mutex_unlock(&mtxExit);
       }
    create new thread()
    ....
    }

Код всегда застревает на cond_wait. :(

РЕДАКТИРОВАТЬ:

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

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

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

Вот где я сталкиваюсь с проблемами.

Это мой подход:

Запустите поток, пусть он сделает свое дело.

поток проверяет на каждом этапе своей работы, актуален ли он. Вот тут-то и на сцену выходит «выход». Основной поток устанавливает для «exit» значение 1, если ему нужно сообщить потоку, что он больше не актуален.

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

Таким образом, основной поток устанавливает значение «exit» и должен дождаться завершения потока. Я не хочу использовать pthread_kill с 0 в качестве сигнала, потому что тогда основной поток будет в цикле, тратя циклы ЦП. Мне нужен основной поток, чтобы отказаться от управления и спать / ждать, пока поток не завершится.

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

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

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

Я ценю ваше время.


person powerrox    schedule 26.08.2010    source источник
comment
Вы работаете на многопроцессорной (примечание: не обязательно многоядерной) машине?   -  person Andres Jaan Tack    schedule 27.08.2010
comment
Кроме того, как объявляется exit?   -  person Andres Jaan Tack    schedule 27.08.2010
comment
exit - глобальная переменная. Я работаю в многоядерной, многопоточной, многопроцессорной системе. Да, все три верны.   -  person powerrox    schedule 27.08.2010
comment
Не вопрос к вашему ответу, но зачем вы используете потоки, если ваше требование - полностью последовательное поведение?   -  person Schedler    schedule 27.08.2010
comment
Нет, это не последовательное поведение. Есть два параллельных потока управления.   -  person powerrox    schedule 28.08.2010
comment
Отступы - ваш друг ... они помогают построить мысленную модель потока программы.   -  person Attie    schedule 16.03.2017


Ответы (4)


Вы забыли инициализировать переменную условия?

pthread_cond_init(&condVar, NULL)
person Dennis Miller    schedule 26.08.2010
comment
Я использовал статическую инициализацию PTHREAD_COND_INITIALIZER - person powerrox; 27.08.2010
comment
Моя условная переменная также объявлена ​​глобальной переменной, поскольку у меня одновременно не может быть более одного активного потока. - person powerrox; 27.08.2010

while (exit == 1) {

В коде, который вы цитируете, в том, как вы цитируете, я не вижу особой проблемы. Он не чистый, но выглядит функциональным. Что заставляет меня думать, что где-то еще вы устанавливаете exit в 0, не сигнализируя об этом. Или нить где-то застревает, выполняя работу.

Но, учитывая комментарии, которые намекают, что вы пытаетесь сигнализировать о завершении одного потока перед запуском другого потока, я думаю, что вы делаете это неправильно. Обычно не следует полагаться на сигнализацию состояния pthread, если сигнал не может быть пропущен. Хотя кажется, что переменная состояния exit покрывает это, это все же неправильное применение IMO условий pthread.

В случае, если вы можете попробовать использовать семафоры. При завершении поток увеличивает семафор завершения, так что main может ждать (уменьшать) семафор.

thread function()
{
   for (condition)
   {
      do work
      /* should the thread continue? */
      if (exit == 1) {
         break; /* exit for */
      } 
   } /* end for */
   sem_post(&termSema);
}

function main()
{
    if (thread is still active)
    {
          exit = 1;
          sem_wait(&termSema);
          exit = 0;
    }
    create new thread()
    ....
}

В качестве общего замечания я могу предложить поискать некоторые реализации пула потоков. Потому что использование переменной состояния для синхронизации потоков по-прежнему неверно и не масштабируется до более чем одного потока. И подвержен ошибкам.

person Dummy00001    schedule 26.08.2010
comment
@powerrox: semaphore advise стоит, даже с новым комментарием. Если поток завершается, сема уже увеличивается, и main не будет ждать. Если поток все еще работает, main будет блокироваться при декременте - ожидая, когда поток увеличит семафор. - person Dummy00001; 27.08.2010

Когда код застрял в pthread_cond_wait, это exit 1 или 0? Если exit равен 1, он должен зависнуть.

Если exit равно 0, скорее всего, имеет место одно из двух:

1) Некоторый код устанавливает exit в 0, но не сигнализирует о переменной условия.

2) Какой-то поток заблокирован на pthread_cond_wait, принял сигнал, но не сделал того, что вам нужно.

person David Schwartz    schedule 30.08.2011

У вас есть всевозможные проблемы с синхронизацией с вашей текущей реализацией (отсюда и проблемы).

Чтобы убедиться, что поток завершен (и его ресурсы освобождены), вы должны вызвать pthread_join().

Здесь нет необходимости в pthread_cond_t.

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

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

void *thread_func(void *arg) {
    int i;

    for (i = 0; i < 10; i++) {
        /* protect any regions that must not be cancelled... */
        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

        /* very important work */
        printf("%d\n", i);

        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

        /* force a check to see if we're finished */
        pthread_testcancel();

        /* sleep (for clarity in the example) */
        sleep(1);
    }

    return NULL;
}

void main(void) {
    int ret;
    pthread_t tid;

    ret = pthread_create(&tid, NULL, thread_func, NULL);
    if (ret != 0) {
        printf("pthread_create() failed %d\n", ret);
        exit(1);
    }

    sleep(5);

    ret = pthread_cancel(tid);
    if (ret != 0) {
        printf("pthread_cancel() failed %d\n", ret);
        exit(1);
    }

    ret = pthread_join(tid, NULL);
    if (ret != 0) {
        printf("pthread_join() failed %d\n", ret);
        exit(1);
    }

    printf("finished...\n");
}

Также стоит отметить:

  • exit() - это библиотечная функция - вы не должны объявлять что-либо с тем же именем, что и что-то еще.
  • В зависимости от вашей конкретной ситуации может иметь смысл поддерживать один поток в активном состоянии всегда и предоставлять ему задания, которые нужно выполнять, вместо того, чтобы постоянно создавать / отменять потоки (исследуйте «пулы потоков»)
person Attie    schedule 16.03.2017