異步編程舉例之多線程版本鬧鐘

 

現在讓我們看一個和多進程版本相似的鬧鐘程序,但它是用多線程實現的。該例子中用到的三個Pthreads函數:

pthread_create : 創建一個線程,運行由第三個參數(alarm_thread)指定的例程(具體見下面例子),並返回線程標識符ID(保存在thread引用的變量中)

 pthread_detach : 當線程終止時立刻回收線程資源

 pthread_exit: 終止線程調用

 

數據結構alarm_t中定義了每個鬧鐘的命令信息,seconds中存儲等待時間,message中存儲顯示文本。

#include <pthread.h>

#include "errors.h"

 

typedef struct alarm_tag {

    int         seconds;

    char        message[64];

} alarm_t;

 

函數alarm_thread是鬧鐘線程,即創建的每個鬧鐘線程執行的函數,該函數返回時,鬧鐘線程終止。該函數參數(void *arg)是傳給pthread_create函數的第四個參數,即alarm_t結構體指針。線程首先將void* 參數轉爲alarm_t* 類型,然後調用pthread_detach函數來分離自己,作用是通知Pthreads不必關心它的終止時間與退出狀態。

線程睡眠指定的時間(由alarm_t 中的seconds決定),之後打印指定的消息文本,最後釋放alarm_t結構體空間並返回,線程終止。通常,Pthreads會保存線程的資源以供其他線程瞭解它已經終止並獲得其最終結果。由於本例中線程負責分離自己,所以不必做上述工作。

 

void *alarm_thread (void *arg)

{

    alarm_t *alarm = (alarm_t*)arg;

    int status;

 

    status = pthread_detach (pthread_self ());

    if (status != 0)

        err_abort (status, "Detach thread");

    sleep (alarm->seconds);

    printf ("(%d) %s\n", alarm->seconds, alarm->message);

    free (alarm);

    return NULL;

}

 

線程版本鬧鐘的main()函數與之前的兩個版本相同,循環讀取命令行、解析命令行直到不能從stdin中讀取數據爲止。

創建一個鬧鐘線程,它以alarm_t爲線程參數運行函數alarm_thread。

int main()

{

    int status;

    char line[128];

    alarm_t *alarm;

    pthread_t thread;

 

    while (1) {

        printf ("Alarm> ");

        if (fgets (line, sizeof (line), stdin) == NULL) exit (0);

        if (strlen (line) <= 1) continue;

        alarm = (alarm_t*)malloc (sizeof (alarm_t));

        if (alarm == NULL)

            errno_abort ("Allocate alarm");

 

        if (sscanf (line, "%d %64[^\n]",

                    &alarm->seconds, alarm->message) < 2) {

            fprintf (stderr, "Bad command\n");

            free (alarm);

        } else {

            status = pthread_create (

                        &thread, NULL, alarm_thread, alarm);

            if (status != 0)

                err_abort (status, "Create alarm thread");

        }

    }

}

 

總結:比較兩個異步版本鬧鐘程序是理解線程編程很好的選擇。在fork版本中,每個鬧鐘有一個從主進程拷貝的獨立地址空間,這意味着可以將鬧鐘時間和顯示文本放在局部變量中,一旦創建了子進程,父進程就可以改變這些變量而不會影響鬧鐘子進程。在多線程版本中,所有線程共享同一個地址空間,所以可爲每個鬧鐘調用malloc建立新的alarm_t結構體,並傳遞給新建線程。

在使用fork()版本中,主進程要調用waitpid函數來通知系統釋放其創建的子進程資源。在多線程版本中,不需要等待線程結束,除非希望獲得它的返回值;每個線程分離自己,故該線程的資源在它終止後會立刻回收。

在實際應用中,不會爲每個鬧鐘建立一個進程。你可能輕易設置上百個鬧鐘活動,但是系統可能無法創建那麼多進程。但是對應可以在一個進程中創建幾百個線程。

另外可以將常用的頭文件以及一些宏定義包含在一個頭文件中,比如#include "errors.h"。本次程序的運行環境依然是Qt的控制檯程序。

彩蛋:一個更加成熟的鬧鐘版本可以只有兩個線程:一個負責讀取用戶輸入,一個等待鬧鐘停止。之後的學習會逐步實現該版本。

 

歡迎大家共同交流:

發現在微信上不適合看程序,讀文字還好~~~

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章