线程与线程同步
Posted 行稳方能走远
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程与线程同步相关的知识,希望对你有一定的参考价值。
目录
上一章,学习了进程相关的知识内容,对进程有了一个比较全面的认识和理解;本章开始,将学习Linux应用编程中非常重要的编程技巧—线程(Thread);与进程类似,线程是允许应用程序并发执行多个任务的一种机制,线程参与系统调度,事实上,系统调度的最小单元是线程、而并非进程。虽然线程的概念比较简单,但是其所涉及到的内容比较多。
线程概述
线程概念
什么是线程?
线程是参与系统调度的最小单位。它被包含在进程之中,是进程中的实际运行单位。一个线程指的是进程中一个单一顺序的控制流(或者说是执行路线、执行流),一个进程中可以创建多个线程,多个线程实现并发运行,每个线程执行不同的任务。譬如某应用程序设计了两个需要并发运行的任务task1 和task2,可将两个不同的任务分别放置在两个线程中。
线程是如何创建起来的?
当一个程序启动时,就有一个进程被操作系统(OS)创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程(Main Thread),因为它是程序一开始时就运行的线程。应用程序都是以main()做为入口开始运行的,所以main()函数就是主线程的入口函数,main()函数所执行的任务就是主线程需要执行的任务。
所以由此可知,任何一个进程都包含一个主线程,只有主线程的进程称为单线程进程,譬如前面章节内容中所编写的所有应用程序都是单线程程序,它们只有主线程;既然有单线程进程,那自然就存在多线程进程,所谓多线程指的是除了主线程以外,还包含其它的线程,其它线程通常由主线程来创建(调用
pthread_create 创建一个新的线程),那么创建的新线程就是主线程的子线程。
主线程的重要性体现在两方面:
⚫ 其它新的线程(也就是子线程)是由主线程创建的;
⚫ 主线程通常会在最后结束运行,执行各种清理工作,譬如回收各个子线程。
线程的特点?
线程是程序最基本的运行单位,而进程不能运行,真正运行的是进程中的线程。当启动应用程序后,系统就创建了一个进程,可以认为进程仅仅是一个容器,它包含了线程运行所需的数据结构、环境变量等信息。
同一进程中的多个线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack,我们称为线程栈),自己的寄存器环境(register
context)、自己的线程本地存储(thread-local storage)。
在多线程应用程序中,通常一个进程中包括了多个线程,每个线程都可以参与系统调度、被CPU 执行,线程具有以下一些特点:
⚫ 线程不单独存在、而是包含在进程中;
⚫ 线程是参与系统调度的基本单位;
⚫ 可并发执行。同一进程的多个线程之间可并发执行,在宏观上实现同时运行的效果;
⚫ 共享进程资源。同一进程中的各个线程,可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量等等。
线程与进程?
进程创建多个子进程可以实现并发处理多任务(本质上便是多个单线程进程),多线程同样也可以实现(一个多线程进程)并发处理多任务的需求,那我们究竟选择哪种处理方式呢?首先我们就需要来分析下多进程和多线程两种编程模型的优势和劣势。
多进程编程的劣势:
⚫进程间切换开销大。多个进程同时运行(指宏观上同时运行,无特别说明,均指宏观上),微观上依然是轮流切换运行,进程间切换开销远大于同一进程的多个线程间切换的开销,通常对于一些中小型应用程序来说不划算。
⚫ 进程间通信较为麻烦。每个进程都在各自的地址空间中、相互独立、隔离,处在于不同的地址空间中,因此相互通信较为麻烦,在上一章节给大家有所介绍。
解决方案便是使用多线程编程,多线程能够弥补上面的问题:
⚫ 同一进程的多个线程间切换开销比较小。
⚫ 同一进程的多个线程间通信容易。它们共享了进程的地址空间,所以它们都是在同一个地址空间中,通信容易。
⚫ 线程创建的速度远大于进程创建的速度。
⚫ 多线程在多核处理器上更有优势!
终上所述,多线程编程相比于多进程编程的优势是比较明显的,在实际的应用当中多线程远比多进程应用更为广泛。那既然如此,为何还存在多进程编程模型呢?难道多线程编程就不存在缺点吗?当然不是,多线程也有它的缺点、劣势,譬如多线程编程难度高,对程序员的编程功底要求比较高,因为在多线程环境下需要考虑很多的问题,例如线程安全问题、信号处理的问题等,编写与调试一个多线程程序比单线程程序困难得多。
当然除此之外,还有一些其它的缺点,这里就不再一一列举了。多进程编程通常会用在一些大型应用程序项目中,譬如网络服务器应用程序,在中小型应用程序中用的比较少。
并发和并行
在前面的内容中,曾多次提到了并发这个概念,与此相类似的概念还有并行、串行,这里和大家聊一聊这些概念含义的区别。
对于串行比较容易理解,它指的是一种顺序执行,譬如先完成task1,接着做task2、直到完成task2,然后做task3、直到完成task3……依次按照顺序完成每一件事情,必须要完成上一件事才能去做下一件事,只有一个执行单元,这就是串行运行。
并行与串行则截然不同,并行指的是可以并排/并列执行多个任务,这样的系统,它通常有多个执行单元,所以可以实现并行运行,譬如并行运行task1、task2、task3。
并行运行并不一定要同时开始运行、同时结束运行,只需满足在某一个时间段上存在多个任务被多个执行单元同时在运行着,譬如:
相比于串行和并行,并发强调的是一种时分复用,与串行的区别在于,它不必等待上一个任务完成之后在做下一个任务,可以打断当前执行的任务切换执行下一个任何,这就是时分复用。在同一个执行单元上,将时间分解成不同的片段(时间片),每个任务执行一段时间,时间一到则切换执行下一个任务,依次这样轮训(交叉/交替执行),这就是并发运行。如下图所示:
笔者在网络上看到了很多比较有意思、形象生动的比喻,用来说明串行、并行以及并发这三个概念的区别,这里笔者截取其中的一个:
⚫ 你吃饭吃到一半,电话来了,你一直到吃完了以后才去接电话,这就说明你不支持并发也不支持并行,仅仅只是串行。
⚫ 你吃饭吃到一半,电话来了,你停下吃饭去接了电话,电话接完后继续吃饭,这说明你支持并发。
⚫ 你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
这里再次进行总结:
⚫ 串行:一件事、一件事接着做
⚫ 并发:交替做不同的事;
⚫ 并行:同时做不同的事。
需要注意的是,并行运行情况下的多个执行单元,每一个执行单元同样也可以以并发方式运行。
从通用角度上介绍完这三个概念之后,类比到计算机系统中,首先我们需要知道两个前提条件:
⚫ 多核处理器和单核处理器:对于单核处理器来说,只有一个执行单元,同时只能执行一条指令;而对于多核处理起来说,有多个执行单元,可以并行执行多条指令,譬如8 核处理器,那么可以并行执行8 条不同的指令。
⚫ 计算机操作系统中,通常同时运行着几十上百个不同的线程,在单核或多核处理系统中都是如此!
对于单核处理器系统来说,它只有一个执行单元(譬如I.MX6U 硬件平台,单核Cortex-A7 SoC),只能采用并发运行系统中的线程,而肯定不可能是串行,而事实上确实如此。内核实现了调度算法,用于控制系统中所有线程的调度,简单点来说,系统中所有参与调度的线程会加入到系统的调度队列中,它们由内核控制,每一个线程执行一段时间后,由系统调度切换执行调度队列中下一个线程,依次进行。在前面章节内容中也给大家有简单地提到过系统调用的问题,关于更加详细的内容,这里便不再介绍了,我们只需有个大概的认识、了解即可!
对于多核处理器系统来说,它拥有多个执行单元,在操作系统中,多个执行单元以并行方式运行多个线程,同时每一个执行单元以并发方式运行系统中的多个线程。
同时运行
计算机处理器运行速度是非常快的,在单个处理核心虽然以并发方式运行着系统中的线程(微观上交替
/交叉方式运行不同的线程),但在宏观上所表现出来的效果是同时运行着系统中的所有线程,因为处理器的运算速度太快了,交替轮训一次所花费的时间在宏观上几乎是可以忽略不计的,所以表示出来的效果就是同时运行着所有线程。
这就好比现实生活中所看到的一些事情,它所给带来的视角效果,譬如一辆车在高速上行驶,有时你会感觉到车的轮毂没有转动,一种视角暂留现象,因为车轮转动速度太快了,人眼是看不清的,会感觉车轮好像是静止的,事实上,车轮肯定是在转动着。
本小节的内容到这里就结束了,理解了本小节的内容,对于后面内容的将会有很大的帮助、也可以帮助大家快速理解后面的内容,大家加油!
线程ID
就像每个进程都有一个进程ID 一样,每个线程也有其对应的标识,称为线程ID。进程ID 在整个系统中是唯一的,但线程ID 不同,线程ID 只有在它所属的进程上下文中才有意义。
进程ID 使用pid_t 数据类型来表示,它是一个非负整数。而线程ID 使用pthread_t 数据类型来表示,一个线程可通过库函数pthread_self()来获取自己的线程ID,其函数原型如下所示:
#include <pthread.h>
pthread_t pthread_self(void);
使用该函数需要包含头文件<pthread.h>。
该函数调用总是成功,返回当前线程的线程ID。
可以使用pthread_equal()函数来检查两个线程ID 是否相等,其函数原型如下所示:
#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);
如果两个线程ID t1 和t2 相等,则pthread_equal()返回一个非零值;否则返回0。在Linux 系统中,使用无符号长整型(unsigned long int)来表示pthread_t 数据类型,但是在其它系统当中,则不一定是无符号长整型,所以我们必须将pthread_t 作为一种不透明的数据类型加以对待,所以pthread_equal()函数用于比较两个线程ID 是否相等是有用的。
线程ID 在应用程序中非常有用,原因如下:
⚫ 很多线程相关函数,譬如后面将要学习的pthread_cancel()、pthread_detach()、pthread_join()等,它们都是利用线程ID 来标识要操作的目标线程;
⚫ 在一些应用程序中,以特定线程的线程ID 作为动态数据结构的标签,这某些应用场合颇为有用,既可以用来标识整个数据结构的创建者或属主线程,又可以确定随后对该数据结构执行操作的具体线程。
创建线程
启动程序时,创建的进程只是一个单线程的进程,称之为初始线程或主线程,本小节我们讨论如何创建一个新的线程。
主线程可以使用库函数pthread_create()负责创建一个新的线程,创建出来的新线程被称为主线程的子线程,其函数原型如下所示:
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
使用该函数需要包含头文件<pthread.h>。
函数参数和返回值含义如下:
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 *,并且该函数的参数只有一个void *,其实这个参数就是pthread_create()
函数的第四个参数arg。如果需要向start_routine()传递的参数有一个以上,那么需要把这些参数放到一个结构体中,然后把这个结构体对象的地址作为arg 参数传入。
arg:传递给start_routine()函数的参数。一般情况下,需要将arg 指向一个全局或堆变量,意思就是说在线程的生命周期中,该arg 指向的对象必须存在,否则如果线程中访问了该对象将会出现错误。当然也可将参数arg 设置为NULL,表示不需要传入参数给start_routine()函数。
返回值:成功返回0;失败时将返回一个错误号,并且参数thread 指向的内容是不确定的。
注意pthread_create()在调用失败时通常会返回错误码,它并不像其它库函数或系统调用一样设置errno,每个线程都提供了全局变量errno 的副本,这只是为了与使用errno 到的函数进行兼容,在线程中,从函数中返回错误码更为清晰整洁,不需要依赖那些随着函数执行不断变化的全局变量,这样可以把错误的范围限制在引起出错的函数中。
线程创建成功,新线程就会加入到系统调度队列中,获取到CPU 之后就会立马从start_routine()函数开始运行该线程的任务;调用pthread_create()函数后,通常我们无法确定系统接着会调度哪一个线程来使用CPU 资源,先调度主线程还是新创建的线程呢(而在多核CPU 或多CPU 系统中,多核线程可能会在不同的核心上同时执行)?如果程序对执行顺序有强制要求,那么就必须采用一些同步技术来实现。这与前面学习父、子进程时也出现了这个问题,无法确定父进程、子进程谁先被系统调度。
使用示例
#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)
printf("新线程: 进程ID<%d> 线程ID<%lu>\\n", getpid(), pthread_self());
return (void *)0;
int main(void)
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, new_thread_start, 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);
应该将pthread_t 作为一种不透明的数据类型加以对待,但是在示例代码中需要打印线程ID,所以要明确其数据类型,示例代码中使用了printf()函数打印线程ID 时,将其作为unsigned long int 数据类型,在Linux系统下,确实是使用unsigned long int 来表示pthread_t,所以这样做没有问题!
主线程休眠了1 秒钟,原因在于,如果主线程不进行休眠,它就可能会立马退出,这样可能会导致新创建的线程还没有机会运行,整个进程就结束了。
在主线程和新线程中,分别通过getpid()和pthread_self()来获取进程ID 和线程ID,将结果打印出来,运行结果如下所示:
编译时出现了错误,提示“对‘pthread_create’未定义的引用”,示例代码确实已经包含了<pthread.h>头文件,但为什么会出现这样的报错,仔细看,这个报错是出现在程序代码链接时、而并非是编译过程,所以可知这是链接库的文件,如何解决呢?
gcc -o testApp testApp.c -lpthread
使用-l 选项指定链接库pthread,原因在于pthread 不在gcc 的默认链接库中,所以需要手动指定。再次编译便不会有问题了,如下:
从打印信息可知,正如前面所介绍那样,两个线程的进程ID 相同,说明新创建的线程与主线程本来就属于同一个进程,但是它们的线程ID 不同。从打印结果可知,Linux 系统下线程ID 数值非常大,看起来像是一个指针。
终止线程
在示例代码11.3.1 中,我们在新线程的启动函数(线程start 函数)new_thread_start()通过return 返回之后,意味着该线程已经终止了,除了在线程start 函数中执行return 语句终止线程外,终止线程的方式还有多种,可以通过如下方式终止线程的运行:
⚫ 线程的start 函数执行return 语句并返回指定值,返回值就是线程的退出码;
⚫ 线程调用pthread_exit()函数;
⚫ 调用pthread_cancel()取消线程(将在11.6 小节介绍);
如果进程中的任意线程调用exit()、_exit()或者_Exit(),那么将会导致整个进程终止,这里需要注意!
pthread_exit()函数将终止调用它的线程,其函数原型如下所示:
#include <pthread.h>
void pthread_exit(void *retval);
使用该函数需要包含头文件<pthread.h>。
参数retval 的数据类型为void *,指定了线程的返回值、也就是线程的退出码,该返回值可由另一个线程通过调用pthread_join()来获取;同理,如果线程是在start 函数中执行return 语句终止,那么return 的返回值也是可以通过pthread_join()来获取的。
参数retval 所指向的内容不应分配于线程栈中,因为线程终止后,将无法确定线程栈的内容是否有效;出于同样的理由,也不应在线程栈中分配线程start 函数的返回值。
调用pthread_exit()相当于在线程的start 函数中执行return 语句,不同之处在于,可在线程start 函数所调用的任意函数中调用pthread_exit()来终止线程。如果主线程调用了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_start(void *arg)
printf("新线程start\\n");
sleep(1);
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, "Error: %s\\n", strerror(ret));
exit(-1);
printf("主线程end\\n");
pthread_exit(NULL);
exit(0);
新线程中调用sleep()休眠,保证主线程先调用pthread_exit()终止,休眠结束之后新线程也调用pthread_exit()终止,编译测试看看打印结果:
正如上面介绍到,主线程调用pthread_exit()终止之后,整个进程并没有结束,而新线程还在继续运行。
回收线程
在父、子进程当中,父进程可通过wait()函数(或其变体waitpid())阻塞等待子进程退出并获取其终止状态,回收子进程资源;而在线程当中,也需要如此,通过调用pthread_join()函数来阻塞等待线程的终止,并获取线程的退出码,回收线程资源;pthread_join()函数原型如下所示:
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
使用该函数需要包含头文件<pthread.h>。
函数参数和返回值含义如下:
thread:pthread_join()等待指定线程的终止,通过参数thread(线程ID)指定需要等待的线程;
retval:如果参数retval 不为NULL,则pthread_join()将目标线程的退出状态(即目标线程通过
pthread_exit()退出时指定的返回值或者在线程start 函数中执行return 语句对应的返回值)复制到retval 所指向的内存区域;如果目标线程被pthread_cancel()取消,则将PTHREAD_CANCELED 放在retval 中。如果对目标线程的终止状态不感兴趣,则可将参数retval 设置为NULL。
返回值:成功返回0;失败将返回错误码。
调用pthread_join()函数将会以阻塞的形式等待指定的线程终止,如果该线程已经终止,则pthread_join()
立刻返回。如果多个线程同时尝试调用pthread_join()等待指定线程的终止,那么结果将是不确定的。
若线程并未分离(detached,将在11.6.1 小节介绍),则必须使用pthread_join()来等待线程终止,回收线程资源;如果线程终止后,其它线程没有调用pthread_join()函数来回收该线程,那么该线程将变成僵尸线程,与僵尸进程的概念相类似;同样,僵尸线程除了浪费系统资源外,若僵尸线程积累过多,那么会导致应用程序无法创建新的线程。
当然,如果进程中存在着僵尸线程并未得到回收,当进程终止之后,进程会被其父进程回收,所以僵尸线程同样也会被回收。
所以,通过上面的介绍可知,pthread_join()执行的功能类似于针对进程的waitpid()调用,不过二者之间存在一些显著差别:
⚫ 线程之间关系是对等的。进程中的任意线程均可调用pthread_join()函数来等待另一个线程的终止。譬如,如果线程A 创建了线程B,线程B 再创建线程C,那么线程A 可以调用pthread_join()等待线程C 的终止,线程C 也可以调用pthread_join()等待线程A 的终止;这与进程间层次关系不同,父进程如果使用fork()创建了子进程,那么它也是唯一能够对子进程调用wait()的进程,线程之间不存在这样的关系。
⚫ 不能以非阻塞的方式调用pthread_join()。对于进程,调用waitpid()既可以实现阻塞方式等待、也可以实现非阻塞方式等待。
使用示例
#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)
printf("新线程start\\n");
sleep(2);
printf("新线程end\\n");
pthread_exit((void *)10);
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_create()创建新线程之后,新线程执行new_thread_start()函数,而在主线程中调用pthread_join()阻塞等待新线程终止,新线程终止后,pthread_join()返回,将目标线程的退出码保存在*tret 所指向的内存中。测试结果如下:
取消线程
在通常情况下,进程中的多个线程会并发执行,每个线程各司其职,直到线程的任务完成之后,该线程中会调用pthread_exit()退出,或在线程start 函数执行return 语句退出。
有时候,在程序设计需求当中,需要向一个线程发送一个请求,要求它立刻退出,我们把这种操作称为取消线程,也就是向指定的线程发送一个请求,要求其立刻终止、退出。譬如,一组线程正在执行一个运算,一旦某个线程检测到错误发生,需要其它线程退出,取消线程这项功能就派上用场了。
本小节就来讨论Linux 系统下的线程取消机制。
取消一个线程
通过调用pthread_cancel()库函数向一个指定的线程发送取消请求,其函数原型如下所示:
#include <pthread.h>
int pthread_cancel(pthread_t thread);
使用该函数需要包含头文件<pthread.h>,参数thread 指定需要取消的目标线程;成功返回0,失败将返回错误码。
发出取消请求之后,函数pthread_cancel()立即返回,不会等待目标线程的退出。默认情况下,目标线程也会立刻退出,其行为表现为如同调用了参数为PTHREAD_CANCELED(其实就是(void *)-1)的pthread_exit()函数,但是,线程可以设置自己不被取消或者控制如何被取消(11.6.2 小节介绍),所以pthread_cancel()并不会等待线程终止,仅仅只是提出请求。
使用示例
#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)
printf("新线程--running\\n");
for ( ; ; )
sleep(1);
return (void *)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);
sleep(1);
/* 向新线程发送取消请求*/
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_start()函数直接运行for 死循环;主线程休眠一段时间后,调用pthread_cancel()向新线程发送取消请求,接着再调用pthread_join()等待新线程终止、获取其终止状态,将线程退出码打印出来。测试结果如下:
由打印结果可知,当主线程发送取消请求之后,新线程便退出了,而且退出码为-1,也就是PTHREAD_CANCELED。
取消状态以及类型
默认情况下,线程是响应其它线程发送过来的取消请求的,响应请求然后退出线程。当然,线程可以选择不被取消或者控制如何被取消,通过pthread_setcancelstate()和pthread_setcanceltype()来设置线程的取消性状态和类型。
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type, int *oldtype);
使用这些函数需要包含头文件<pthread.h>,pthread_setcancelstate()函数会将调用线程的取消性状态设置为参数state 中给定的值,并将线程之前的取消性状态保存在参数oldstate 指向的缓冲区中,如果对之前的状态不感兴趣,Linux 允许将参数oldstate 设置为NULL;pthread_setcancelstate()调用成功将返回0,失败返回非0 值的错误码。
pthread_setcancelstate()函数执行的设置取消性状态和获取旧状态操作,这两步是一个原子操作。
参数state 必须是以下值之一:
⚫ PTHREAD_CANCEL_ENABLE:线程可以取消,这是新创建的线程取消性状态的默认值,所以新建线程以及主线程默认都是可以取消的。
⚫ PTHREAD_CANCEL_DISABLE:线程不可被取消,如果此类线程接收到取消请求,则会将请求挂起,直至线程的取消性状态变为PTHREAD_CANCEL_ENABLE。
使用示例
修改示例代码11.6.1,在新线程的new_thread_start()函数中调用pthread_setcancelstate()函数将线程的取消性状态设置为PTHREAD_CANCEL_DISABLE,我们来试试,此时主线程还能不能取消新线程,示例代码如下所示:
#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)
线程间的通信同步方式与进程间通信方式