Posix pthread worker, который запускается только при необходимости

У меня есть рабочий поток, выполняющий опрос (он нацелен на libcurl, но это не имеет значения).

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

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

Код:

pthread_t thread = NULL;
struct timespec sleep_val = {0, 20000000};
void *worker_thread(void *threadid)
{
    do {
        poll();
        nanosleep(&sleep_val, NULL);
    } while (no_of_handles_running > 0);
    pthread_exit(NULL);
    thread = NULL;
}

void start_worker_thread_if_needed() {
    if (thread == NULL) {
        pthread_create(&thread, NULL, worker_thread, NULL);
    }
}

Я сомневаюсь в безопасности потоков. Функцию start_worker_thread_if_needed() можно вызывать в любое время любое количество раз.

Итак, что, если start_worker_thread_if_needed() вызывается именно тогда, когда мы выходим из цикла while, и тем самым прерывает рабочий поток. Если это произойдет, условие if (thread == NULL) будет FALSE, поскольку рабочий поток прерван, а pthread_exit + thread = NULL еще не завершен.

Итак, теперь start_worker_thread_if_needed() завершится без создания нового потока, но как только управление будет возвращено старому рабочему потоку, он перейдет к строке pthread_exit, и рабочий поток будет уничтожен.

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

Изменить: вот предложение с мьютексом, но у меня все еще есть те же сомнения. Что произойдет, если основной поток прервется сразу после выхода из цикла while и до того, как рабочий сможет взять мьютекс? Затем основной поток не создает новый поток, а затем рабочий завершает работу.

void *worker_thread(void *threadid)
{
    do {
        poll();
        nanosleep(&sleep_val, NULL);
    } while (no_of_handles_running > 0);
    pthread_mutex_lock(&my_mutex);
    thread = NULL;
    pthread_mutex_unlock(&my_mutex);
    pthread_exit(NULL);
}

void start_worker_thread_if_needed() {
    pthread_mutex_lock(&my_mutex);
    if (thread == NULL) {
        pthread_create(&thread, NULL, worker_thread, NULL);
    }
    pthread_mutex_unlock(&my_mutex);
}

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


person user1816142    schedule 17.02.2016    source источник
comment
Что вы ожидаете увидеть?   -  person HoKy22    schedule 18.02.2016
comment
Для этого и нужны мьютексы. Кроме того, вы ничего не можете сделать после вызова pthread_exit, так как поток завершился, поэтому он не будет выполнять операцию thread = NULL;.   -  person David Schwartz    schedule 18.02.2016
comment
Я ожидал, что другая версия приведенного выше кода решит проблему, которую я пытался описать. Я думал об использовании мьютексов, но не видел, как это решит проблему.   -  person user1816142    schedule 18.02.2016
comment
Да, ваши сомнения вполне обоснованы - то, что у вас есть, называется состоянием гонки. Поскольку к переменной thread обращаются несколько потоков, вам необходимо должным образом защитить доступ к ней. Наиболее распространенный метод - с мьютексом. Подробнее см. pthread_mutex_lock и pthread_mutex_unlock.   -  person kaylum    schedule 18.02.2016
comment
Игнорирование проблемы pthread_exit, на которую указал Дэвид, и игнорирование проблем с видимостью: представьте, что рабочий поток видит no_of_handles_running == 0 и поэтому выходит из цикла, а затем основной поток видит, что рабочий поток работает, поэтому он не запускает его, а затем рабочий поток завершается, не видя новой работы.   -  person user253751    schedule 18.02.2016
comment
@DavidSchwartz, отличный улов с = NULL после thread_exit.   -  person user1816142    schedule 18.02.2016
comment
@immibis, именно эту проблему я и пытаюсь решить. Основной поток считает, что рабочий поток уже существует, поскольку он был прерван перед установкой thread=NULL и, следовательно, не запускает новый. Это то, что я пытаюсь решить.   -  person user1816142    schedule 18.02.2016
comment
@user1816142 user1816142 Использование мьютекса может решить проблему, поскольку гарантирует, что только один поток будет иметь доступ к переменной thread в любой момент времени. Части кода, которые обращаются к общему ресурсу, называются критическим разделом. Все критические секции должны быть защищены путем блокировки мьютекса перед CS и разблокировки после CS.   -  person kaylum    schedule 18.02.2016
comment
@user1816142 user1816142 Это именно та проблема, которую решит мьютекс.   -  person David Schwartz    schedule 18.02.2016
comment
@DavidSchwartz, спасибо за попытку помочь ребятам. Может быть, я просто устал, я добавил пример выше с мьютексом, но я все еще боюсь, что у меня может быть такая же проблема.   -  person user1816142    schedule 18.02.2016
comment
Ваш pthread_mutex_unlock находится не в том месте в start_worker_thread_if_needed. Он должен идти после блока if (т. е. вы всегда должны разблокировать мьютекс). Кроме этого, он должен работать нормально. О, если, конечно, вы правильно инициализировали my_mutex (это не показано).   -  person kaylum    schedule 18.02.2016
comment
Зачем выходить из темы? почему бы треду не спать, пока нечего делать? Я бы рассмотрел возможность использования блокирующего ввода-вывода для отправки сообщений в поток. Поток будет просто блокироваться при чтении ввода-вывода, пока не получит задачу через канал. Вы можете изучить эту библиотеку как пример использования каналов для связи потоков.   -  person Myst    schedule 18.02.2016
comment
@kaylum, извините за разблокировку, это была глупая ошибка. Инициализация не показана, я просто сделал это в качестве быстрого примера. У меня все еще есть ощущение, что если мы выйдем из цикла, но прервемся до того, как рабочий поток сможет взять блокировку, то основной поток возьмет блокировку, увидит, что рабочий поток уже запущен, и ничего не сделает. Затем рабочий может продолжить работу, но теперь он находится вне цикла while, он устанавливает рабочий процесс в NULL и завершает работу. Однако предыдущий вызов из основного потока теряется, так как ни один рабочий не был запущен, а старый уже покинул цикл while :-)   -  person user1816142    schedule 18.02.2016
comment
Похоже, ты можешь быть прав. Но сложно порекомендовать вам полное решение, потому что ваш код неполный. В частности, похоже, что no_of_handles_running также является общей переменной, к которой могут обращаться несколько потоков, что означает, что она также должна быть защищена. Кроме того, как уже было сказано выше ... почему вы вообще выходите из рабочего потока? Почему бы просто не позволить ему периодически просыпаться для проведения опроса (или, что еще лучше, сигнализировать ему о пробуждении, когда нужно выполнить работу)?   -  person kaylum    schedule 18.02.2016
comment
no_of_handles_running безопасен, он используется только внутри функции опроса, которая вызывается только рабочим потоком, так что с этой частью все в порядке. Для частоты мне нужно, чтобы он был супер отзывчивым, когда нужно делать работу, поэтому я не хочу больше спать. С другой стороны, задания могут быть нечастыми, поэтому я не хочу, чтобы поток выполнял опрос 20 мс, если он не нужен.   -  person user1816142    schedule 18.02.2016
comment
Хорошо, но внутри функции опроса должно быть что-то, что используется совместно с другими потоками. Потому что другие потоки должны ставить в очередь работу для функции опроса. Разве это не так? Кроме того, для оперативности вы вообще не должны использовать опрос. Ваш код должен управляться событиями, то есть рабочий поток просыпается именно тогда, когда есть что делать.   -  person kaylum    schedule 18.02.2016
comment
Сигнальная часть вашего комментария интересна. Это для кроссплатформенного проекта. Есть ли такой механизм, встроенный в posix?   -  person user1816142    schedule 18.02.2016
comment
Да, значение активных дескрипторов предоставляется libcurl. Я прошу об этом внутри функции опроса.   -  person user1816142    schedule 18.02.2016
comment
Действительно, libcurl можно использовать с libev, но мой код в настоящее время не поддерживает эту версию.   -  person user1816142    schedule 18.02.2016
comment
Опять же, трудно сказать вам, что делать без полного контекста ваших требований. Существует множество различных способов реализации логики, управляемой событиями. Например, если вы можете отслеживать файловые дескрипторы, то poll или select. Если ваши требования шире, другой вариант — переменные условия — см. pthread_cond_wait и pthread_cond_signal.   -  person kaylum    schedule 18.02.2016
comment
Большое спасибо за ваши предложения. Я прочитаю условные переменные первым делом завтра утром (здесь полночь).   -  person user1816142    schedule 18.02.2016
comment
@кайлум. Большое спасибо за указатель на условные переменные. Думаю, он сделал именно то, что мне было нужно. Выложил окончательный код ниже.   -  person user1816142    schedule 18.02.2016


Ответы (1)


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

Окончательный код (с удаленными журналами) приведен ниже, и он работает нормально.

static int no_of_handles_running;
static int RUN_THREAD = 0;
pthread_t thread = NULL;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cv  = PTHREAD_COND_INITIALIZER;;

void *worker_thread(void *threadid)
{
    RUN_THREAD = 1;
    do {
        poll();  //Will update no_of_handles_running
        if (no_of_handles_running == 0) {
            pthread_mutex_lock(&mutex);
            pthread_cond_wait(&cv, &mutex);
            pthread_mutex_unlock(&mutex);
        }
    } while (RUN_THREAD);
    thread = NULL;
    pthread_exit(NULL);

}
void start_worker_thread() {
    if (thread == NULL) {
        int rc = pthread_create(&thread, NULL, worker_thread, NULL);
        if (rc){
            return;
        } 
    }
    pthread_mutex_lock(&mutex);
    pthread_cond_signal(&cv);
    pthread_mutex_unlock(&mutex);
}

void stop_worker_thread() {
    RUN_THREAD = 0;
    pthread_mutex_lock(&mutex);
    pthread_cond_signal(&cv);
    pthread_mutex_unlock(&mutex);
}
person user1816142    schedule 18.02.2016