1. 问题提出
编写程序模拟 Allen 和 Luffy 爬楼,楼层是从 1-10 层。Allen 每上一层休息 10ms,Luffy 每上一层休息 5ms. (需要注意的是 Allen 和 Luffy 是一起爬楼的,而非某个人先爬楼,爬完后接着另一个人爬楼。)
对于上述问题,利用多进程编程可以很好的解答。我们可以在父进程中执行 Allen 的爬楼过程而在子进程中执行 Luffy 的爬楼过程。
但是!如果要求用单进程来实现它,要怎么办?如果在单进程中写这样的代码,你应该怎么去实现?(假设你还不知道多线程的概念。)
看起来是不是手足无措?
好了,如果真不让你用多线程,估计这个问题你也很难解决。不过,你要知道在 Linux 2.4 内核之前,操作系统是不提供线程支持的(请参考《Linux 中 pthread 线程库历史》 ),你只能使用 pthread 线程函数库来创建线程(用户级线程),这也就是说,再没有多线程库的情况下,也能解决此问题。所以,不使用多线程库来解决它的方案,暂且作为一个难题抛出来。
2. 解决思路
使用 pthread 线程库,利用多线程解决爬楼问题。
这里只要粗略了掌握创建线程的函数 pthread_create 就行,有关此函数的知识,后文将会给出详细的介绍。
typedef void *(*start_routine) (void *);
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, start_routine th_fn, void *arg);
这里先大致介绍一下各个参数:
- thread: 传出参数,用来存储线程 id(你可以类比进程 id),通常它是个整数。
- attr: 传入参数,用来设置线程属性。本文实验,先传空指针。
- th_fn: 线程入口函数。线程启动后,进入这个函数。
- arg: 线程入口函数的参数。
该函数返回 0 表示成功。
3. 程序清单
- 代码
// hellothread.c
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
// 线程入口函数
void* th_fn(void *arg) {
char* name = (char*)arg;
int i;
for (i = 1; i <= 10; ++i) {
printf("%s : %d\n", name, i);
// 判断是 Allen 还是 Luffy
if (strcmp("Allen", name) == 0)
usleep(1000*10); // 10 ms
else
usleep(1000*5); // 5ms
}
return NULL;
}
int main() {
int err;
pthread_t allen, luffy;
char *name1 = "Allen";
char *name2 = "Luffy";
// 创建 allen 线程。
err = pthread_create(&allen, NULL, th_fn, (void*)name1);
if (err != 0) {
perror("pthread_create");
return -1;
}
// 创建 luffy 线程
err = pthread_create(&luffy, NULL, th_fn, (void*)name2);
if (err != 0) {
perror("pthread_create");
return -1;
}
// 问题:如果没有此行,会发生什么?
sleep(3);
// 打印线程 id 号
printf("Allen id: %lx\n", allen);
printf("Luffy id: %lx\n", luffy);
return 0;
}
- 编程和运行
// 注意需要链接 pthread 库
$ gcc hellothread.c -o hellothread -lpthread
$ ./hellothread
- 运行结果
不出意外,我们能看到 Luffy 总是先到达 10 楼 ^_^. 另外,每次运行的结果可能都不一样。
4. 总结
- 初步理解什么是多线程
- 知道线程有自己的 id 号
- 认识到多进程和多线程的不同
- 线程被创建完成后,线程函数什么时候被执行,哪个线程先被调度是未知的。
练习1:使用多进程完成本文中的实验。
练习2:使用多线程完成本文中的实验。
练习3:删除多线程代码中 main 函数里的 sleep(3),观察效果。