2013-09-29 49 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()將所中斷。

2

的一種方式,我看到的是民調閱讀標準輸入()選擇()有一些小超時。當超時返回時,如果設置爲「false」值,則可以檢查「正在運行」標誌並執行清除終止。

可能不是最好的解決方案,但它應該工作。

+1

謝謝。我沒有使用這些方法,但是這促使我考慮讓fgetc調用非阻塞,然後我找到了解決方案。 – DanielGibbs

1

我設法找到了適合我的解決方案:我從標準輸入非阻塞讀取。

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

這也需要某種形式的在後臺線程sleep(或usleepnanosleep)。

感謝HapKoM讓我朝着正確的方向思考。

2

有兩種方法去:

  • 你改變你的代碼不會阻止(使用O_NONBLOCK,則poll(),選擇()等),
  • 你強迫你的受阻代碼來獲得退出阻塞系統調用。

強制系統調用的結束基本上可以通過兩種方式來完成:要麼你做的系統調用無法完成(例如,您關閉文件描述符上阻塞調用等待),或者你發送一些發信號給該線程,並確保信號處理已正確設置。正確的信號處理意味着信號不會被忽略,不會被屏蔽,並且信號標誌的設置方式使信號處理後系統調用不會重新啓動(請參閱sigaction(),SA_RESTART標誌)。