2013-09-29 3 views
2

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

В настоящее время моя программа ловит SIGINT, SIGTERM и SIGPIPE, чтобы закончить программу чисто. Моя проблема заключается в том, что в конце основного потока (как только основной цикл завершился из обработчика сигнала), он пытается вернуть настройки терминала с помощью tcsetattr, однако это блокируется до тех пор, пока текущий fgetc не вызовет другой поток.

Как я могу прерывать фоновый поток так, чтобы вызов fgetc возвращался, и основной поток мог восстановить настройки терминала и выйти из него чисто?

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

Соответствующий код:

// If the program should still be running. 
static sig_atomic_t running = 1; 

// Background thread that reads keypresses. 
pthread_t thread; 

static void *get_keypresses(); 

static void receive_signal(int signal) { 
    (void)signal; 
    running = 0; 
} 

int main(int argc, char *argv[]) { 
    // Set up signal handling. 
    if(signal(SIGINT, receive_signal) == SIG_ERR) { 
     fprintf(stderr, "Error setting signal handler for SIGINT.\n"); 
    } 
    if(signal(SIGTERM, receive_signal) == SIG_ERR) { 
     fprintf(stderr, "Error setting signal handler for SIGTERM.\n"); 
    } 
    if(signal(SIGPIPE, receive_signal) == SIG_ERR) { 
     fprintf(stderr, "Error setting signal handler for SIGPIPE.\n"); 
    } 

    // Set up thread attributes. 
    pthread_attr_t thread_attrs; 
    if(pthread_attr_init(&thread_attrs) != 0) { 
     perror("Unable to create thread attributes"); 
     exit(2); 
    } 
    if(pthread_attr_setdetachstate(&thread_attrs, PTHREAD_CREATE_DETACHED) != 0) { 
     perror("Unable to set thread attributes"); 
     exit(2); 
    } 

    // Set up terminal for reading keypresses. 
    struct termios orig_term_attr; 
    struct termios new_term_attr; 
    tcgetattr(fileno(stdin), &orig_term_attr); 
    memcpy(&new_term_attr, &orig_term_attr, sizeof(struct termios)); 
    new_term_attr.c_lflag &= ~(ECHO|ICANON); 
    tcsetattr(fileno(stdin), TCSANOW, &new_term_attr); 

    // Start background thread to read keypresses. 
    if((pthread_create(&thread, &thread_attrs, &get_keypresses, NULL)) != 0) { 
     perror("Unable to create thread"); 
     exit(2); 
    } 

    // Main loop. 
    while(running) { 
     // Read data from network and output to screen. 
    } 

    // Restore original terminal attributes. ***IT BLOCKS HERE*** 
    tcsetattr(fileno(stdin), TCSANOW, &orig_term_attr); 

    return 0; 
} 

// Get input from the keyboard. 
static void *get_keypresses() { 
    int c; 
    while(running) { 
     // Get keypress. ***NEEDS TO BE INTERRUPTED HERE*** 
     if((c = fgetc(stdin)) != - 1) { 
      // Handle keypress. 
     } 
    } 
    return NULL; 
} 
+0

Здесь есть сообщение, описывающее метод, который можно использовать для прерывания. Это можно использовать в сочетании с флагом или подобным набором, например, тем, что предложил @HapKom. Также предлагайте использовать Sleep(); после того, как вы выполнили действие в своей рабочей функции потока, например. прямо под вашим комментарием '// Handle keypress'. – ryyker

ответ

1

Заменить

if((c = fgetc(stdin)) != -1) 

с

if (0 < read(fileno(stdin), &c, 1)) 

read() будет interupted, если поток получает сигнал.

2

Один из способов, который я вижу, это прочитать на стандартный ввод опроса() или выберите() с некоторой небольшой тайм-аут. И когда он вернется в таймаут, вы можете проверить флаг «running» и выполнить четкое завершение, если оно установлено на «false».

Может быть, это не лучшее решение, но оно должно работать.

+1

Спасибо. Я не использовал эти методы, но это побудило меня задуматься о том, чтобы сделать вызов «fgetc» неблокирующим, а затем я ударил решение. – DanielGibbs

1

Мне удалось найти решение, которое хорошо работает для меня: я сделал чтение со стандартного ввода без блокировки.

fcntl(fileno(stdin), F_SETFL, O_NONBLOCK); 

Это также требует некоторой формы sleep (или usleep или nanosleep) в фоновом потоке.

Спасибо за HapKoM за то, что вы задумали в правильном направлении.

2

Есть два пути:

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

Для того, сигнал к этому потоку и убедитесь, что обработка сигнала настроена правильно. Правильная обработка сигналов означает, что сигнал не игнорируется, а не замаскирован и что флаги сигналов настроены таким образом, что системный вызов не запускается после обработки сигнала (см. Флаг sigaction(), SA_RESTART).

Смежные вопросы