目录
线程是参与系统调度的最小单位,它被包含在进程之中, 是进程中的实际运行单位。一个进程中可以创建多个线程, 多个线程实现并发运行, 每个线程执行不同的任务。
线程是程序最基本的运行单位,而进程不能运行, 真正运行的是进程中的线程。 当启动应用程序后,系统就创建了一个进程,可以认为进程仅仅是一个容器, 它包含了线程运行所需的数据结构、环境变量等信息。
线程不单独存在、而是包含在进程中;
(1)线程是参与系统调度的基本单位;
(2)可并发执行。同一进程的多个线程之间可并发执行,在宏观上实现同时运行的效果;
(3)共享进程资源。 同一进程中的各个线程,可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量等等。
一个进程可以通过fork()函数等创建子进程去实现并发处理多任务的功能,多个子进程的本质是单线程进程,一个进程中包含多个线程同样也可以实现并发处理的功能。
多线程编程劣势:
① 进程间的切换开销比较大。
② 进程间的通信较为麻烦,因为多个进程通常位于自己独立的地址空间。
多线程的优势:
① 同一进程中的多个线程由于都存在与同一片地址空间,因此线程间切换开销比较小。
② 同一进程中多个线程通信比较容易。
③ 线程创建的速度远快于进程。
运行一个程序时,创建的进程相当于一个单线程进程,这个线程可以称为主线程。在系统库函数中提供了pthread_create(),可以实现主线程创建新的子线程,函数原型如下:
- #include <pthread.h>
-
- int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
-
- thread: pthread_t 类型指针, 当 pthread_create()成功返回时,新创建的线程的线程 ID 会保存在参数 thread所指向的内存中,后续的线程相关函数会使用该标识来引用此线程。
- attr: pthread_attr_t 类型指针,指向 pthread_attr_t 类型的缓冲区, pthread_attr_t 数据类型定义了线程的各种属性,关于线程属性将会在 11.8 小节介绍。如果将参数 attr 设置为 NULL, 那么表示将线程的所有属性设置为默认值,以此创建新线程。
- start_routine: 参数 start_routine 是一个函数指针,指向一个函数, 新创建的线程从start_routine()函数开始运行,该函数返回值类型为void *。
- arg: 传递给 start_routine()函数的参数。一般情况下,需要将 arg 指向一个全局或堆变量,意思就是说
- 在线程的生命周期中,该 arg 指向的对象必须存在,否则如果线程中访问了该对象将会出现错误。 当然也可
- 将参数 arg 设置为 NULL,表示不需要传入参数给 start_routine()函数。
将终止调用它的线程,其函数原型如下所示:
- #include <pthread.h>
- void pthread_exit(void *retval);
-
- 参数 retval 的数据类型为 void *,指定了线程的返回值、也就是线程的退出码.
线程创建成功, 新线程就会加入到系统调度队列中,获取到 CPU 之后就会立马从 start_routine()函数开始运行该线程的任务。
测试代码:
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <unistd.h>
-
- static void *new_thread(void *arg)
- {
- printf("新线程: 进程 ID<%d> 线程 ID<%lu>\n", getpid(), pthread_self());
- pthread_exit(NULL);
- }
-
- int main(void)
- {
- pthread_t tid;
- int ret;
- ret = pthread_create(&tid, NULL, new_thread, NULL);
- if (ret) {
- fprintf(stderr, "Error: %s\n", strerror(ret));
- exit(-1);
- }
- printf("主线程: 进程 ID<%d> 线程 ID<%lu>\n", getpid(), pthread_self());
- sleep(1);
- exit(0);
- }
运行结果如下:

从图中可以看出,两个线程的进程号相同,这是因为它们都属于同一个进程。
在进程中,父进程通常需要使用wait()函数,等待子进程结束后回收其资源。对于线程来说,通过调用 pthread_join()函数来阻塞等待线程的终止,并获取线程的退出码, 回收线程资源,其函数原型如下:
- #include <pthread.h>
- int pthread_join(pthread_t thread, void **retval);
-
- 函数参数和返回值含义如下:
- thread: pthread_join()等待指定线程的终止,通过参数 thread(线程 ID) 指定需要等待的线程;
- retval: 如果参数 retval 不为 NULL,则 pthread_join()将目标线程的退出状态(即目标线程通过
- pthread_exit()退出时指定的返回值或者在线程 start 函数中执行 return 语句对应的返回值)复制到*retval 所指向的内存区域;如果目标线程被 pthread_cancel()取消, 则将 PTHREAD_CANCELED 放在*retval 中。
- 返回值: 成功返回 0;失败将返回错误码。
##由于同一个进程中任意线程的关系是对等的。所以进程中的任意线程都可以调用此函数等待另一个线程的结束##
测试代码:
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <unistd.h>
-
-
- static void *new_thread(void *arg)
- {
- printf("新线程 start\n");
- sleep(2);
- printf("新线程 end\n");
- pthread_exit((void *)20); //线程返回值,通过tret获取。
- }
-
- int main(void)
- {
- pthread_t tid; // 保存线程号
- void *tret; //线程返回值
-
- int ret;
- ret = pthread_create(&tid, NULL, new_thread, NULL);
- if(ret) { //创建线程失败,在标准错误打印错误码
- fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
- exit(-1);
- }
-
- ret = pthread_join(tid, &tret); //回收线程
- if (ret) { //线程回收失败
- fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
- exit(-1);
- }
- printf("新线程终止, code=%ld\n", (long)tret);
- exit(0);
- }
主线程调用 pthread_create()创建新线程之后,新线程执行 new_thread_start()函数,而在主线程中调用pthread_join()阻塞等待新线程终止,新线程终止后, pthread_join()返回,将目标线程的退出码保存在*tret 所指向的内存中。
运行结果如下:

一个线程可以通过调用 pthread_cancel()库函数向一个指定的线程发送取消请求,其函数原型如下所示:
- #include <pthread.h>
- int pthread_cancel(pthread_t thread);
-
- 发出取消请求之后,函数 pthread_cancel()立即返回,不会等待目标线程的退出。默认情况下,目标线程
- 也会立刻退出,其行为表现为如同调用了参数为 PTHREAD_CANCELED(其实就是(void *)-1) 的
- pthread_exit()函数
测试代码:
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <unistd.h>
-
-
- static void *new_thread(void *arg)
- {
- printf("新线程 start\n");
- while(1) sleep(1);
- pthread_exit((void *)20); //线程返回值,通过tret获取。
- }
-
- int main(void)
- {
- pthread_t tid; // 保存线程号
- void *tret; //线程返回值
-
- int ret;
- ret = pthread_create(&tid, NULL, new_thread, NULL);
- if(ret) { //创建线程失败,在标准错误打印错误码
- fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
- exit(-1);
- }
-
- sleep(2);
-
- /* 向新线程发送取消请求 */
- ret = pthread_cancel(tid);
- if (ret) {
- fprintf(stderr, "pthread_cancel error: %s\n", strerror(ret));
- exit(-1);
- }
-
-
- ret = pthread_join(tid, &tret); //回收线程
- if (ret) { //线程回收失败
- fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
- exit(-1);
- }
- printf("新线程终止, code=%ld\n", (long)tret);
- exit(0);
- }
主线程创建新线程,新线程 new_thread()函数直接运行死循环;主线程休眠一段时间后,调用pthread_cancel()向新线程发送取消请求,接着再调用 pthread_join()等待新线程终止、获取其终止状态,将线程退出码打印出来。
测试结果如下:

线程可以通过pthread_setcancelstate()函数设置为不会被取消,函数原型如下:
- #include <pthread.h>
- int pthread_setcancelstate(int state, int *oldstate);
-
- pthread_setcancelstate()函数执行的设置取消性状态和获取旧状态操作,这两步是一个原子操作。
- 参数 state 必须是以下值之一:
- PTHREAD_CANCEL_ENABLE: 线程可以取消,这是新创建的线程取消性状态的默认值,所以
- 新建线程以及主线程默认都是可以取消的。
- PTHREAD_CANCEL_DISABLE: 线程不可被取消,如果此类线程接收到取消请求,则会将请求
- 挂起,直至线程的取消性状态变为 PTHREAD_CANCEL_ENABLE。
测试代码:
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <unistd.h>
-
-
- static void *new_thread(void *arg)
- {
- printf("新线程 start\n");
- pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
- while(1) {
- printf("新线程 running\n");
- sleep(1);
- }
- pthread_exit((void *)20); //线程返回值,通过tret获取。
- }
-
- int main(void)
- {
- pthread_t tid; // 保存线程号
- void *tret; //线程返回值
-
- int ret;
- ret = pthread_create(&tid, NULL, new_thread, NULL);
- if(ret) { //创建线程失败,在标准错误打印错误码
- fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
- exit(-1);
- }
-
- sleep(2);
-
- /* 向新线程发送取消请求 */
- ret = pthread_cancel(tid);
- if (ret) {
- fprintf(stderr, "pthread_cancel error: %s\n", strerror(ret));
- exit(-1);
- }
-
-
- ret = pthread_join(tid, &tret); //回收线程
- if (ret) { //线程回收失败
- fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
- exit(-1);
- }
- printf("新线程终止, code=%ld\n", (long)tret);
- exit(0);
- }
新线程 new_thread()函数中调用 pthread_setcancelstate()将自己设置为不可被取消,主线程延时2秒钟之后调用 pthread_cancel()向新线程发送取消请求,那么此时新线程是不会终止的, pthread_cancel()立刻返回之后进入到 pthread_join()函数,那么此时会被阻塞等待新线程终止。
运行结果如下:

如果线程的取消性状态为 PTHREAD_CANCEL_ENABLE,那么对取消请求的处理则取决于线程的取消性类型,该类型可以通过调用 pthread_setcanceltype()函数来设置,它的参数 type 指定了需要设置的类型,而线程之前的取消性类型则会保存在参数 oldtype 所指向的缓冲区中,如果不需要之前的取消类型可以设置为NULL。
- #include <pthread.h>
-
- int pthread_setcanceltype(int type, int *oldtype);
-
- pthread_setcanceltype()函数执行的设置取消性类型和获取旧类型操作,这两步是一个原子操作。
- 参数 type 必须是以下值之一:
- PTHREAD_CANCEL_DEFERRED: 取消请求到来时,线程还是继续运行,取消请求被挂起,直
- 到线程到达某个取消点(cancellation point,将在 11.6.3 小节介绍) 为止,这是所有新建线程包括
- 主线程默认的取消性类型。
- PTHREAD_CANCEL_ASYNCHRONOUS: 可能会在任何时间点(也许是立即取消,但不一定)
如果需要查看哪些函数是取消点,可以通过man 7 pthreads命令进行查看。

当线程终止时,其它线程可以通过调用 pthread_join()获取其返回状态、回收线程资源。如果希望系统在线程终止时能够自动回收线程资源并将其移除,则可以调佣pthread_detach()函数:
- #include <pthread.h>
- int pthread_detach(pthread_t thread);
测试代码:
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <unistd.h>
-
-
- static void *new_thread_start(void *arg)
- {
- int ret;
- /* 分离自行分离 */
- ret = pthread_detach(pthread_self());
- if (ret) {
- fprintf(stderr, "pthread_detach error: %s\n", strerror(ret));
- return NULL;
- }
- printf("新线程 start\n");
- sleep(2); //休眠 2 秒钟
- printf("新线程 end\n");
- pthread_exit(NULL);
- }
-
-
- int main(void)
- {
- pthread_t tid;
- int ret;
- /* 创建新线程 */
- ret = pthread_create(&tid, NULL, new_thread_start, NULL);
- if (ret) {
- fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
- exit(-1);
- }
- sleep(1); //休眠 1 秒钟
- /* 等待新线程终止 */
- ret = pthread_join(tid, NULL);
- if (ret)
- fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
- pthread_exit(NULL);
- }
主线程创建新的线程之后,休眠 1 秒钟,调用 pthread_join()等待新线程终止;新线程调用
pthread_detach(pthread_self())将自己分离,休眠 2 秒钟之后 pthread_exit()退出线程;主线程休眠 1 秒钟是能够确保调用 pthread_join()函数时新线程已经将自己分离了,所以按照上面的介绍可知,此时主线程调用pthread_join()必然会失败。
测试结果如下:

当线程终止退出时,去执行处理函数,这个称为线程清理函数。线程通过函数 pthread_cleanup_push()和 pthread_cleanup_pop()分别负责向调用线程的清理函数栈中添加
和移除清理函数,函数原型如下所示:
- #include <pthread.h>
-
- void pthread_cleanup_push(void (*routine)(void *), void *arg);
- void pthread_cleanup_pop(int execute);
-
- 调用 pthread_cleanup_push()向清理函数栈中添加一个清理函数,第一个参数 routine 是一个函数指
-
- 针,指向一个需要添加的清理函数, routine()函数无返回值,只有一个 void *类型参数;第二个参
-
- 数 arg,当调用清理函数 routine()时, 将 arg 作为 routine()函数的参数。
-
- 函数 pthread_cleanup_pop()的 execute 参数,可以取值为 0,也可以为非 0;如果为 0,清理函数
-
- 不会被调用,只是将清理函数栈中最顶层的函数移除;如果参数 execute 为非 0,则除了将清理函数
-
- 栈中最顶层的函数移除之外,还会该清理函数。
测试代码:
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <unistd.h>
-
- static void cleanup(void *arg)
- {
- printf("cleanup: %s\n", (char *)arg);
- }
-
- static void *new_thread_start(void *arg)
- {
- printf("新线程--start run\n");
- pthread_cleanup_push(cleanup, "第 1 次调用");
- pthread_cleanup_push(cleanup, "第 2 次调用");
- pthread_cleanup_push(cleanup, "第 3 次调用");
- pthread_cleanup_pop(1); //执行最顶层的清理函数
- printf("~~~~~~~~~~~~~~~~~\n");
- sleep(2);
- pthread_exit((void *)0); //线程终止
- /* 为了与 pthread_cleanup_push 配对 */
- pthread_cleanup_pop(0);
- pthread_cleanup_pop(0);
- }
-
-
- int main(void)
- {
- pthread_t tid;
- void *tret;
- int ret;
- /* 创建新线程 */
- ret = pthread_create(&tid, NULL, new_thread_start, NULL);
- if (ret) {
- fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
- exit(-1);
- }
- /* 等待新线程终止 */
- ret = pthread_join(tid, &tret);
- if (ret) {
- fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
- exit(-1);
- }
- printf("新线程终止, code=%ld\n", (long)tret);
- exit(0);
- }
在新线程调用 pthread_exit()之前,先调用 pthread_cleanup_pop(1)手动运行了最顶层的清理
函数,并将其从栈中移除,并且清理函数执行的顺序和栈是一样的,先进后出,测试结果:
