嘗試再增加一個(gè)線(xiàn)程
上一篇文章中我們舉了一個(gè)用C開(kāi)發(fā)多線(xiàn)程程序的例子,并同時(shí)給出了這個(gè)程序的Linux版本和Windows版本。我們現(xiàn)在希望在這個(gè)例子基礎(chǔ)上再增加一個(gè)線(xiàn)程,這個(gè)線(xiàn)程每隔2秒鐘計(jì)算一次整數(shù)級(jí)數(shù)求和,并在標(biāo)準(zhǔn)輸出上打印結(jié)果。
// File : thread2.c
#include <stdio.h>
#include <unistd.h>
extern volatile int g_iQuitFlag;
const int MAX_I = 65534;
int Sum(int n)
{
if(n==0) {
return 0;
} else {
return n + Sum(n-1);
}
}
void *thread2_function(void *arg) {
for(int i=1; (g_iQuitFlag != 1) && (i<MAX_I); ++i) {
int sum = Sum(i);
printf("Sum(1-->%d) = %d.\n", i, sum);
sleep(2);
}
return NULL;
}
//--EOF--
線(xiàn)程2調(diào)用遞歸函數(shù)計(jì)算整數(shù)級(jí)數(shù)求和,如果到達(dá)可計(jì)算范圍的上限就退出。
相應(yīng)地,main.c文件也需要作一些修改。
// File : main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
volatile int g_iQuitFlag;
extern void *thread1_function(void *arg);
extern void *thread2_function(void *arg);
int main()
{
char szBuf[1024];
int iRes;
pthread_t t1, t2;
iRes = pthread_create(&t1, NULL, thread1_function, NULL);
if (iRes != 0) {
perror("Calling pthread_create failed.");
exit(-1);
}
iRes = pthread_create(&t2, NULL, thread2_function, NULL);
if (iRes != 0) {
perror("Calling pthread_create failed.");
pthread_cancel(t1);
exit(-1);
}
while(true) {
fgets(szBuf, sizeof(szBuf), stdin);
if(0 == strncmp(szBuf, "quit", 4)) {
g_iQuitFlag = 1;
break;
}
}
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}
//--EOF--
main函數(shù)兩次調(diào)用pthread_create分別創(chuàng)建兩個(gè)線(xiàn)程。如果第二個(gè)線(xiàn)程創(chuàng)建失敗,則調(diào)用pthread_cancel強(qiáng)制結(jié)束第一個(gè)線(xiàn)程,然后調(diào)用exit結(jié)束程序。一般來(lái)說(shuō)應(yīng)該謹(jǐn)慎使用pthread_cancle,因?yàn)槿绻唤Y(jié)束線(xiàn)程沒(méi)有適當(dāng)對(duì)應(yīng)很可能會(huì)因?yàn)橘Y源沒(méi)有釋放而造成程序死鎖等嚴(yán)重問(wèn)題。本程序中只是為了簡(jiǎn)單示例,所以沒(méi)有什么問(wèn)題。
Windows版本的程序代碼從略。
現(xiàn)在我們?cè)賮?lái)重新審視一下我們編寫(xiě)的程序。我們發(fā)現(xiàn),首先,程序幾乎是不可移植的。我們已經(jīng)說(shuō)過(guò),與線(xiàn)程有關(guān)的系統(tǒng)調(diào)用都是和操作系統(tǒng)緊密相關(guān)的。我們現(xiàn)在給出了Linux(嚴(yán)格地說(shuō)是Linux平臺(tái)的Posix)和Windows版本的多線(xiàn)程程序,看起來(lái)程序變動(dòng)不大。然而在實(shí)際的開(kāi)發(fā)項(xiàng)目中,為了移植而作這種底層API的變動(dòng)非常耗時(shí)而且很容易產(chǎn)生Bug,通常遇到這種情況還不如為各自的平臺(tái)重新定制來(lái)得省事。
C++這種OO語(yǔ)言具有封裝的特性,封裝性可以用來(lái)隱藏對(duì)象內(nèi)部數(shù)據(jù),也可以用來(lái)隱蔽操作系統(tǒng)相關(guān)的特性。我們可以想象,如果我們?cè)O(shè)計(jì)了一個(gè)隱蔽了操作系統(tǒng)特性的線(xiàn)程類(lèi),我們就可以不費(fèi)吹灰之力來(lái)完成平臺(tái)移植的代碼修改工作---至少與線(xiàn)程相關(guān)代碼基本不用修改。
我們?cè)賮?lái)看一下這兩個(gè)線(xiàn)程的例程函數(shù)。線(xiàn)程1和線(xiàn)程2的結(jié)構(gòu)比較類(lèi)似,二者都進(jìn)入一個(gè)循環(huán),每隔一段時(shí)間就去做一些處理,然后每次都檢測(cè)程序退出標(biāo)志以判斷是否應(yīng)該退出線(xiàn)程循環(huán)。然而我們不得不為兩個(gè)線(xiàn)程編寫(xiě)各自的例程函數(shù),盡管它們?nèi)绱说南嘞?。另外,這個(gè)例子中兩個(gè)線(xiàn)程都檢查全局的程序退出標(biāo)志,而實(shí)際應(yīng)用中一般都需要分別控制各個(gè)線(xiàn)程的執(zhí)行,在必要的時(shí)候結(jié)束特定的某個(gè)線(xiàn)程。
本例中的這樣的線(xiàn)程被稱(chēng)作后臺(tái)線(xiàn)程(background threads),因?yàn)樗鼈儾恢苯雍陀脩?hù)打交道,雖然它們使用了標(biāo)準(zhǔn)輸出,但這只是本例的一個(gè)簡(jiǎn)單化設(shè)計(jì)。后臺(tái)線(xiàn)程的控制相對(duì)簡(jiǎn)單,它們一般只需要在某個(gè)特定的時(shí)間點(diǎn)被執(zhí)行,然后在特定的時(shí)間點(diǎn)退出。相比之下,前臺(tái)線(xiàn)程(foreground threads),也就是響應(yīng)用戶(hù)事件的線(xiàn)程(如本例中的主線(xiàn)程),需要更為復(fù)雜的控制機(jī)制,一般采用異步通信機(jī)制和事件響應(yīng)編程模式。
一般而言,一個(gè)程序可能會(huì)有多個(gè)后臺(tái)線(xiàn)程,而只有一個(gè)甚至沒(méi)有前臺(tái)線(xiàn)程。回想一下我們的線(xiàn)程類(lèi)設(shè)計(jì)目標(biāo),簡(jiǎn)單,實(shí)用,方便移植,我們決定只為后臺(tái)線(xiàn)程建模設(shè)計(jì)類(lèi),因?yàn)楹笈_(tái)線(xiàn)程模型比較簡(jiǎn)單,易于實(shí)現(xiàn),而且設(shè)計(jì)的收益也比較大。對(duì)于前臺(tái)線(xiàn)程,可以將其視為特殊的后臺(tái)線(xiàn)程,通過(guò)在后臺(tái)線(xiàn)程模型的基礎(chǔ)上增加事件響應(yīng)模型的擴(kuò)展方法來(lái)進(jìn)行對(duì)應(yīng)。
聯(lián)系客服