第一个多线程程序

第一个多线程程序

c语言中文网:第一个多线程程序

为了避免多个程序访问系统资源(包括文件资源、I/O 设备、网络等)时产生冲突,操作系统会将可能产生冲突的系统资源保护起来,阻止应用程序直接访问。如果程序中需要访问被操作系统保护起来的资源,需使用操作系统规定的方法(函数、命令),我们习惯将这些调用方法(函数、命令)称为接口(Application Programming Interface,简称 API)。(程序要访问资源,需要使用操作系统支持的调用方法,也就是符合操作系统支持的接口(API)来访问)

事实上,无论我们用哪种编程语言编写多线程程序,最终都要借助操作系统预留的接口实现。接下来,我们将为您讲解如何借助 Linux 系统预留的接口编写 C 语言多线程程序。

POSIX标准

类 UNIX 系统有很多种版本,包括 Linux、FreeBSD、OpenBSD 等,它们预留的系统调用接口各不相同。但幸运的是,几乎所有的类 UNIX 系统都兼容 POSIX 标准。

POSIX 标准全称“Portable Operating System Interface”,中文译为可移植操作系统接口,最后的字母 X 代指类 UNIX 操作系统。简单地理解,POSIX 标准发布的初衷就是为了统一所有类 UNIX 操作系统的接口,这意味着,只要我们编写的程序严格按照 POSIX 标准调用系统接口,它就可以在任何兼容 POSIX 标准的类 UNIX 系统上运行。

所谓兼容,很多支持 POSIX 标准的类 UNIX 操作系统并没有从根本上修改自己的 API,它们仅仅通过对现有的 API 进行再封装,生成了一套符合 POSIX 标准的系统接口,进而间接地支持 POSIX 标准。

值得一提的是,POSIX 标准中规范了与多线程相关的系统接口。我们在 Linux 系统上编写多线程程序,只需在程序中引入<pthread.h>头文件,调用该文件中包含的函数即可实现多线程编程。

注意,pthread.h 头文件中只包含各个函数的声明部分,具体实现位于 libpthread.a 库中。

第一个多线程程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <stdio.h>
#include <pthread.h>
// include <iostream> // c++加一行
//定义线程要执行的函数,arg 为接收线程传递过来的数据
void *Thread1(void *arg)
{
printf("http://c.biancheng.net\n");
return "Thread1成功执行"; // C++ 改为:return const_cast<char*>("Thread1成功执行");
}
//定义线程要执行的函数,arg 为接收线程传递过来的数据
void* Thread2(void* arg)
{
printf("C语言中文网\n");
return "Thread2成功执行"; // C++ 改为:return const_cast<char*>("Thread2成功执行");
}

int main()
{
int res;
pthread_t mythread1, mythread2;
void* thread_result;
/*创建线程
&mythread:要创建的线程
NULL:不修改新建线程的任何属性
ThreadFun:新建线程要执行的任务
NULL:不传递给 ThreadFun() 函数任何参数

返回值 res 为 0 表示线程创建成功,反之则创建失败。
*/
res = pthread_create(&mythread1, NULL, Thread1, NULL);
if (res != 0) {
printf("线程创建失败");
return 0;
}

res = pthread_create(&mythread2, NULL, Thread2, NULL);
if (res != 0) {
printf("线程创建失败");
return 0;
}
/*
等待指定线程执行完毕
mtThread:指定等待的线程
&thead_result:接收 ThreadFun() 函数的返回值,或者接收 pthread_exit() 函数指定的值

返回值 res 为 0 表示函数执行成功,反之则执行失败。
*/
res = pthread_join(mythread1, &thread_result); // 令主线程等待 mythread1 线程执行完毕后再执行后续的代码
//输出线程执行完毕后返回的数据
printf("%s\n", (char*)thread_result);

res = pthread_join(mythread2, &thread_result); // 令主线程等待 mythread2 线程执行完毕后再执行后续的代码
printf("%s\n", (char*)thread_result);
printf("主线程执行完毕\n");
return 0;
}


假设我们将程序编写在 thead.c 文件中,调用 gcc 编译器编译(包含链接)此程序:

[root@localhost ~]# gcc thread.c -o thread.exe -lpthread (C++版则是 g++ thread.cc -o thread.exe -lpthread

在保证程序没有语法错误的前提下,执行此命令会生成一个名为 thread.exe 的可执行文件。需要强调的是,命令中必须包含 “-lpthread” 参数,否则会导致程序链接失败。

在当前目录下找到新生成的 thread.exe 文件,执行如下命令即可看到程序的执行结果:

1
2
3
4
5
6
[root@localhost ~]# ./thead.exe
http://c.biancheng.net
C语言中文网
Thread1成功执行
Thread2成功执行
主线程执行完毕

程序中共存在 3 个线程,包括本就存在的主线程以及两个调用 pthread_create() 函数创建的线程(又称子线程),其中名为 mythread1 的线程负责执行 thread1() 函数,名为 mythread2 的线程负责执行 thread2() 函数。

程序中调用了两次 pthread_join() 函数,第 47 行 pthread_join() 函数的功能是令主线程等待 mythread1 线程执行完毕后再执行后续的代码,第 51 行处 pthread_join() 函数的功能是令主线程等待 mythread2 线程执行完毕后在执行后续的代码。

由此,我们已经学会了如何编写一个简单的多线程程序。

说明:

  • pthread_t mythread :创建一个线程对象,线程名称叫mythread,此时线程还没有和要做的任务绑定;

  • pthread_create(&mythread, NULL, Thread1, NULL) : 创建一个线程,将线程对象和任务绑定,任务名叫Thread1`,任务也就是某个函数。

    • 第一个参数:要创建的线程,pthread_t 类型的指针变量,因此如果是线程对象,传入的是对象地址。
    • 第二个参数为 NULL时表示:不修改新建线程的任何属性;
    • 第三个参数 ThreadFun:新建线程要执行的任务(函数);
    • 第四个参数为NULL时表示:不传递给 ThreadFun() 函数任何参数;
    • res = pthread_create(&mythread, NULL, Thread1, NULL); 返回值 res 为 0 表示线程创建成功,反之则创建失败。

    ==pthread_create就开始进入线程,开始执行任务了,和主线程并行的,主线程接着往下走,互不影响。==

  • pthread_join(mythread, &thread_result):执行此语句时,表示主线程必须等待指定线程执行完毕,才能继续接着执行主语句。

    • 第一个参数:指定等待的线程
    • 第二个参数:接收 ThreadFun() 函数的返回值,或者接收 pthread_exit() 函数指定的值
    • res = pthread_join(mythread, &thread_result); 返回值 res 为 0 表示函数执行成功,反之则执行失败。