尝试再增加一个线程 上一篇文章中我们举了一个用C开发多线程程序的例子,并同时给出了这个程序的Linux版本和Windows版本。我们现在希望在这个例子基础上再增加一个线程,这个线程每隔2秒钟计算一次整数级数求和,并在标准输出上打印结果。 // File : thread2.c #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--
线程2调用递归函数计算整数级数求和,如果到达可计算范围的上限就退出。 相应地,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函数两次调用pthread_create分别创建两个线程。如果第二个线程创建失败,则调用pthread_cancel强制结束第一个线程,然后调用exit结束程序。一般来说应该谨慎使用pthread_cancle,因为如果被结束线程没有适当对应很可能会因为资源没有释放而造成程序死锁等严重问题。本程序中只是为了简单示例,所以没有什么问题。
Windows版本的程序代码从略。
现在我们再来重新审视一下我们编写的程序。我们发现,首先,程序几乎是不可移植的。我们已经说过,与线程有关的系统调用都是和操作系统紧密相关的。我们现在给出了Linux(严格地说是Linux平台的Posix)和Windows版本的多线程程序,看起来程序变动不大。然而在实际的开发项目中,为了移植而作这种底层API的变动非常耗时而且很容易产生Bug,通常遇到这种情况还不如为各自的平台重新定制来得省事。
C++这种OO语言具有封装的特性,封装性可以用来隐藏对象内部数据,也可以用来隐蔽操作系统相关的特性。我们可以想象,如果我们设计了一个隐蔽了操作系统特性的线程类,我们就可以不费吹灰之力来完成平台移植的代码修改工作---至少与线程相关代码基本不用修改。
我们再来看一下这两个线程的例程函数。线程1和线程2的结构比较类似,二者都进入一个循环,每隔一段时间就去做一些处理,然后每次都检测程序退出标志以判断是否应该退出线程循环。然而我们不得不为两个线程编写各自的例程函数,尽管它们如此的相像。另外,这个例子中两个线程都检查全局的程序退出标志,而实际应用中一般都需要分别控制各个线程的执行,在必要的时候结束特定的某个线程。
本例中的这样的线程被称作后台线程(background threads),因为它们不直接和用户打交道,虽然它们使用了标准输出,但这只是本例的一个简单化设计。后台线程的控制相对简单,它们一般只需要在某个特定的时间点被执行,然后在特定的时间点退出。相比之下,前台线程(foreground threads),也就是响应用户事件的线程(如本例中的主线程),需要更为复杂的控制机制,一般采用异步通信机制和事件响应编程模式。
一般而言,一个程序可能会有多个后台线程,而只有一个甚至没有前台线程。回想一下我们的线程类设计目标,简单,实用,方便移植,我们决定只为后台线程建模设计类,因为后台线程模型比较简单,易于实现,而且设计的收益也比较大。对于前台线程,可以将其视为特殊的后台线程,通过在后台线程模型的基础上增加事件响应模型的扩展方法来进行对应。
|
|
来自: just_person > 《多线程类库的设计与实现》