POSIX программирование - PullRequest
11 голосов
/ 08 июля 2011

Я должен написать многопоточную (скажем, 2-х ниточную) программу, в которой каждый из этих потоков выполняет свою задачу. Кроме того, эти потоки должны продолжать работать бесконечно в фоновом режиме после запуска. Вот что я сделал. Может кто-нибудь, пожалуйста, дайте мне обратную связь, если метод хорош, и если вы видите некоторые проблемы. Кроме того, я хотел бы знать, как систематически закрывать потоки после завершения выполнения, скажем, с помощью Ctrl + C.

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

Вот скелет:

void    *func1();
void    *func2();

int main(int argc, char *argv[])
{   

    pthread_t th1,th2;
    pthread_create(&th1, NULL, func1, NULL);
    pthread_create(&th2, NULL, func2, NULL);

    fflush (stdout);
    for(;;){
    }
    exit(0); //never reached
}

void *func1()
{
    while(1){
    //do something
    }
}

void *func2()
{
    while(1){
    //do something
    }
} 

Спасибо.

Отредактированный код с использованием входных данных из ответов: Правильно ли я покидаю темы?

#include <stdlib.h>     /*  exit() */
#include <stdio.h>      /* standard in and output*/
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <signal.h>
#include <semaphore.h>

sem_t end;

void    *func1();
void    *func2();

void ThreadTermHandler(int signo){
    if (signo == SIGINT) {
        printf("Ctrl+C detected !!! \n");
        sem_post(&end);
        }
}
void *func1()
{
    int value;
    for(;;){
        sem_getvalue(&end, &value);
        while(!value){
            printf("in thread 1 \n");
        }
    }
    return 0;
}

void *func2()
{
    int value;
    for(;;){
        sem_getvalue(&end, &value);
        while(!value){
            printf("value = %d\n", value);
        }
    }
    return 0;
}

int main(int argc, char *argv[])
{


    sem_init(&end, 0, 0);
    pthread_t th1,th2;
    int value  = -2;
    pthread_create(&th1, NULL, func1, NULL);
    pthread_create(&th2, NULL, func2, NULL);

    struct sigaction sa;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = ThreadTermHandler;
    // Establish a handler to catch CTRL+c and use it for exiting.
    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction for Thread Termination failed");
        exit( EXIT_FAILURE );
    }

    /* Wait for SIGINT. */
    while (sem_wait(&end)!=0){}
    //{
        printf("Terminating Threads.. \n");
        sem_post(&end);
                sem_getvalue(&end, &value);
        /* SIGINT received, cancel threads. */
        pthread_cancel(th1);
        pthread_cancel(th2);
        /* Join threads. */
        pthread_join(th1, NULL);
        pthread_join(th2, NULL);
    //}
    exit(0);
}

Ответы [ 7 ]

13 голосов
/ 05 августа 2011

Существует в основном два подхода для завершения потока.

  • Использовать точку отмены. Поток завершается при запросе на отмену и достигает точки отмены, тем самым завершая выполнение контролируемым образом;
  • Используйте сигнал. Попросите потоки установить обработчик сигнала, который обеспечивает механизм для завершения (установка флага и реагирование на EINTR).

Оба подхода имеют свои недостатки. Подробнее см. Kill Thread в библиотеке Pthread .

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

#define _POSIX_C_SOURCE 200809L
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void sigint(int signo) {
    (void)signo;
}

void *thread(void *argument) {
    (void)argument;
    for (;;) {
        // Do something useful.
        printf("Thread %u running.\n", *(unsigned int*)argument);

        // sleep() is a cancellation point in this example.
        sleep(1);
    }
    return NULL;
}

int main(void) {
    // Block the SIGINT signal. The threads will inherit the signal mask.
    // This will avoid them catching SIGINT instead of this thread.
    sigset_t sigset, oldset;
    sigemptyset(&sigset);
    sigaddset(&sigset, SIGINT);
    pthread_sigmask(SIG_BLOCK, &sigset, &oldset);

    // Spawn the two threads.
    pthread_t thread1, thread2;
    pthread_create(&thread1, NULL, thread, &(unsigned int){1});
    pthread_create(&thread2, NULL, thread, &(unsigned int){2});

    // Install the signal handler for SIGINT.
    struct sigaction s;
    s.sa_handler = sigint;
    sigemptyset(&s.sa_mask);
    s.sa_flags = 0;
    sigaction(SIGINT, &s, NULL);

    // Restore the old signal mask only for this thread.
    pthread_sigmask(SIG_SETMASK, &oldset, NULL);

    // Wait for SIGINT to arrive.
    pause();

    // Cancel both threads.
    pthread_cancel(thread1);
    pthread_cancel(thread2);

    // Join both threads.
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    // Done.
    puts("Terminated.");
    return EXIT_SUCCESS;
}

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

Точки отмены могут быть сложными, если потоки выделяют много ресурсов; в этом случае вам придется использовать pthread_cleanup_push() и pthread_cleanup_pop(), которые являются беспорядком. Но этот подход возможен и довольно элегантен при правильном использовании.

7 голосов
/ 08 июля 2011

Ответ во многом зависит от того, что вы хотите сделать, когда пользователь нажимает Ctrl C .

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

Однако, если ваши потоки должны выполнить очистку, выесть работа.Есть две отдельные проблемы, которые вам нужно рассмотреть:

  • Как вы обрабатываете сигнал и получаете сообщение темам, которые им нужно прекратить.
  • Как ваши потоки получают и обрабатывают запросзавершить.

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

  1. В начале вашей программы инициализируйте семафор с sem_init(&exit_sem, 0, 0);
  2. Установите обработчик сигналов для SIGINT (и любых других сигналов завершения, которые вы хотите обработать, например, SIGTERM), который выполняет sem_post(&exit_sem); и возвращает.
  3. Замените for(;;); в главном потоке на while (sem_wait(&exit_sem)!=0).
  4. После успешного выполнения sem_wait основной поток должен сообщить всем другим потокам, что они должны выйти, а затем дождаться их выхода.

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

Теперь есть несколько способов справиться с информированием рабочего.темы что пора бросать.Некоторые параметры, которые я вижу:

  1. Периодически проверяя sem_getvalue(&exit_sem), очищайте и выходите, если возвращается ненулевое значение.Однако обратите внимание, что это не будет работать, если поток заблокирован на неопределенный срок, например, при вызове read или write.
  2. Использование pthread_cancel, и аккуратно разместите обработчики отмены (pthread_cleanup_push) все
  3. Используйте pthread_cancel, но также используйте pthread_setcancelstate, чтобы отключить отмену в течение большей части вашего кода, и только повторно включить его, когда вы собираетесь выполнять операции ввода-вывода с блокировкой.Таким образом, вам нужно только поместить обработчики очистки только в те места, где разрешено подавление.
  4. Изучите расширенную семантику сигналов и настройте дополнительный обработчик сигналов и сигналов прерывания, который вы отправляете всем потокам через pthread_kill, который будетзаставить системные блокировки возвращаться с ошибкой EINTR.Тогда ваши потоки могут действовать по этому пути и выходить обычным способом C через строку с ошибками, полностью возвращая функцию запуска.

Я бы не рекомендовал подход 4 для начинающих , потому что это трудно понять правильно, но для опытных программистов на Си это может быть лучше, потому что он позволяет вам использовать существующий язык C для сообщения об исключительных условиях с помощью возвращаемых значений, а не «исключений».

Такжеобратите внимание, что с pthread_cancel вам нужно будет периодически вызывать pthread_testcancel, если вы не вызываете какие-либо другие функции, которые точки отмены .В противном случае запрос на отмену никогда не будет обработан.

3 голосов
/ 08 июля 2011

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

  • В "дочерних" потоках используйте pthread_sigmask для блокировки сигналов
  • В главном потоке используйте sigsuspend для ожидания сигнала
  • Как только вы получите сигнал, отмените (pthread_cancel) дочерние темы

Ваш основной поток может выглядеть примерно так:

/* Wait for SIGINT. */
sigsuspend(&mask);

/* SIGINT received, cancel threads. */
pthread_cancel(th1);
pthread_cancel(th2);

/* Join threads. */
pthread_join(th1, NULL);
pthread_join(th2, NULL);

Очевидно, вы должны прочитать больше о pthread_cancel и точках отмены. Вы также можете установить обработчик очистки . И, конечно же, проверьте каждое возвращаемое значение.

3 голосов
/ 08 июля 2011

Это плохая идея:

for(;;){
}

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

Если вам нужно ждать в главном потоке, используйте pthread_join в качестве ответав этом вопросе: Несколько потоков в программе на C

1 голос
/ 24 мая 2012

Не было бы намного проще просто вызвать pthread_cancel и использовать pthread_cleanup_push в функции потока, чтобы потенциально очистить данные, которые были динамически распределены потоком, или выполнить любые задачи завершения, которые требовались до остановки потока.

Так что идея будет:

  1. написать код для обработки сигналов
  2. когда вы делаете ctrl + c ... функция обработки вызывается
  3. эта функция отменяет поток
  4. каждый созданный поток устанавливает функцию очистки потока, используя pthread_cleanup_push
  5. когда протектор отменен, функция pthread_cleanup_push вызывается
  6. присоединиться ко всем темам перед выходом

Кажется, это простое и естественное решение. </p> <pre><code>static void cleanup_handler(void *arg) { printf("Called clean-up handler\n"); } static void *threadFunc(void *data) { ThreadData *td = (ThreadData*)(data); pthread_cleanup_push(cleanup_handler, (void*)something); while (1) { pthread_testcancel(); /* A cancellation point */ ... } pthread_cleanup_pop(cleanup_pop_arg); return NULL; }

1 голос
/ 05 августа 2011

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

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

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

0 голосов
/ 08 июля 2011

Вам не нужна петля в основном. th1->join(); th2->join(); будет достаточно в качестве условия ожидания, поскольку потоки никогда не заканчиваются.

Чтобы остановить потоки, вы можете использовать глобальную общую переменную, такую ​​как bool stop = false;, затем при перехвате сигнала (Ctrl + Z является сигналом в UNIX) установите stop = true прерывание потоков, так как вы ожидаете с join() основная программа также завершится.

пример

void *func1(){
  while(!stop){
    //do something
  }
}
...