現在讓我們看一個和多進程版本相似的鬧鐘程序,但它是用多線程實現的。該例子中用到的三個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的控制檯程序。
彩蛋:一個更加成熟的鬧鐘版本可以只有兩個線程:一個負責讀取用戶輸入,一個等待鬧鐘停止。之後的學習會逐步實現該版本。
歡迎大家共同交流:
發現在微信上不適合看程序,讀文字還好~~~