第一个多线程程序

搞清楚什么是线程之后,从本节开始,正式教大家编写多线程程序。

在 Linux 环境中,可以使用 NPTL 线程库实现多线程编程。NPTL 是 Linux 系统默认的多线程库,完全遵循 POSIX 标准,不需要手动安装。程序中引入<pthread.h>头文件,就可以调用 NPTL 线程库编写多线程程序了。

接下来,先带领大家分析一个简单的多线程程序,然后再分析一个复杂点的多线程程序。学完之后,大家就会对多线程编程有一个整体、直观的认知。

第一个多线程程序

包含多个线程的程序就是多线程程序,比如下面是一个最简单的多线程程序:
#include <stdio.h>
#include <pthread.h>   //pthread_create()
#include <unistd.h>    //sleep()
//线程要执行的函数
void* threadFun(void* arg)
{
    printf("子线程:http://c.biancheng.net\n");
    return NULL;
}

int main()
{
    pthread_t thread; //表示线程的变量
    //手动创建一个新线程,负责执行 threadFun() 函数
    pthread_create(&thread, NULL, threadFun, NULL);
   
    printf("主线程执行完毕\n");
    sleep(5);
    return 0;
}
整个程序包含 2 个线程,一个线程是程序启动时由操作系统创建的,负责执行 main() 函数,通常叫做主线程;另一个是名为 thread 的线程,它是 main() 函数中调用 pthread_create() 函数手动创建的(第 15 行),负责执行 threadFun() 函数,手动创建的线程通常叫做子线程。

pthread_create() 是 <pthread.h> 头文件提供的函数,专门用来创建新的子线程。这里,读者了解 pthread_create() 函数的功能就足够了,有关 pthread_create() 的函数的具体用法,会在后续章节做详细讲解。

程序启动后,主线程开始执行 main() 函数,期间会执行 pthread_create() 函数创建 thread 线程。创建好的 thread 线程会立即开始执行 threadFun() 函数,不需要我们手动启动。这意味着 thread 线程创建成功后,整个进程中同时运行着两个线程,分别是主线程和 thread 线程。

注意,程序第 18 行添加了一个 sleep() 函数,它的功能是使主线程暂停执行 5 秒钟。这样做的好处是,可以确保子线程先执行结束后,主线程才结束执行。反之,如果主线程先一步执行结束,那么子线程将被迫终止执行,子线程可能无法成功执行 printf() 函数,无法在屏幕上输出"子线程:http://c.biancheng.net\n" 这个字符串。

运行多线程程序

假设程序保存在 thread.c 文件中,调用 gcc 编译器编译(包含链接)此程序:

# gcc thread.c -o thread.exe -lpthread

当前目录会生成一个名为 thread.exe 的可执行文件。需要强调的是,命令中必须包含 "-lpthread" 参数,指明程序中用到了 NPTL 线程库,否则程序链接会失败。

在 Windows 环境中,通常用 .exe 作为可执行文件的后缀名;而在 Linux 环境中,文件类型不是通过后缀名来判别的。也就是说,去掉 thread.exe 中的 .exe,生成的还是一个可执行文件。

找到新生成的 thread.exe 文件,执行./thread.exe命令就可以看到程序的运行结果。

运行程序的整个过程如下图所示:


图 1 第一个多线程程序的运行结果

从运行结果可以看出,主线程先执行 printf() 语句输出“主线程执行完毕”,然后 thread 线程执行 printf() 语句输出“子线程:http://c.biancheng.net”。由于 thread 线程执行的很快,受 sleep() 函数的影响,主线程还要等待一段时间后才结束执行。

注意,thread 线程和主线程是一起执行的,它们输出到屏幕上的顺序是不确定的,还可能是 thread 线程先输出 “子线程:http://c.biancheng.net”,然后主线程输出“主线程执行完毕”。

又一个多线程程序

对于包含 2 个线程的多线程程序,一个是操作系统创建的主线程,另一个是手动创建的子线程。接下来我们手动创建多个线程,观察程序的执行过程。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//线程要执行的函数
void* threadFun(void* arg)
{
    printf("线程:%ld\n", pthread_self());
    return NULL;
}

int main()
{
    pthread_t thread[5];//能存储 5 个表示线程的变量

    for (int i = 0; i < 5; i++) {
        //手动创建一个新线程,执行 threadFun() 函数
        pthread_create(&thread[i], NULL, threadFun, NULL);
    }

    printf("主线程执行完毕\n");
    sleep(5);
    return 0;
}
在这个程序的 main() 函数中,循环执行了 5 次 pthread_create() 函数,连续创建了 5 个新线程,每个新建的线程都会立即执行 threadFun() 函数。

再来看 threadFun() 函数,其内部调用了 <pthread.h> 头文件提供的 pthread_self() 函数,它的功能是获取当前线程的 ID 号。类似于我们每个人的身份证号,操作系统会为每个线程分配唯一的标识,称为线程的 ID 号,通常用来区分不同的线程。

和第一个多线程程序一样,我们在 main() 函数的末尾添加了 sleep() 函数,以确保所有子线程先执行结束,主线程最后执行结束。

由于各个线程的执行顺序是不确定的,因此每次执行程序的结果很可能不一样,比如:


图 2 又一个多线程程序的运行结果
仔细观察不难发现:
  • 各个线程是一起运行的,程序每次执行时,各个线程的执行顺序很可能不一样;
  • 程序中每个线程的 ID 号都是唯一的。
  • 程序每次执行都会重新创建各个线程,操作系统也会重新分配各个线程的 ID 号。

总结

对于任意一个多线程线程,主线程是操作系统创建的,其它线程需要调用 pthread_create() 函数手动创建。

在多线程程序中,任意时刻可能有多个线程同时运行,它们的执行顺序是不确定的,程序每次执行的结果也可能不一样。

本节带领大家分析了两个多线程程序,如果你都搞清楚了,那么恭喜你,你已经成功踹开了多线程编程的大门。

关  闭