前言
在上一节中,我们介绍了线程的相关概念及使用方式。比如,如何创建线程。以及多线程编程引入的一致性问题,为了解决该问题,我们也介绍了几种同步方式:互斥量、读写锁、条件变量、自旋锁、屏障。及适合的使用场景,但也仅仅介绍了基本的使用方式。本章再深入了解其中一些机制,以及特殊场景的应用。
线程属性
我们在进行多线程编程时,线程的创建,一般采用以下方式:
pthread_t tidp;
pthread_create(&tidp,NULL,my_thread,NULL);
//pthread_create 函数原型
/**
int pthread_create(pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(*start_rtn)(void*),
void *restrict arg);
*/
其中参数二attr
很少关注,默认传入NULL
。本节我们就一起探讨该参数对线程的影响。熟练掌握其中属性,可以在特定场景,大大提高我们程序的效率。
该参数决定了线程的相关属性,定义如下:
typedef sturct {
int detachstate; //线程是否分离同步
int schedpolicy; //线程的调度策略
struct sched_param sched_param; //线程的调度参数
int inheritsched; //线程的调度策略的继承性
int scope; //线程竞争CPU的范围
size_t guardsize; //线程栈空间末尾缓冲区的大小
int stackaddr_set; //栈设置
void* sockaddr; //栈位置
size_t stacksize; //栈大小(默认8192KB = 8M)
} pthread_attr_t;
线程是否分离
在之前的章节中,我们了解到,线程退出时,会保留一些线程状态供其它线程回收。其它线程可通过pthread_join
接口主动获回收状态,或调用pthread_detach
设置线程退出后,由底层自动回收。其它线程若不调用这两个接口,资源则无法回收,若频繁的线程创建与退出,则会出现内存问题,导致程序运行异常。
detachstate
取值范围有两个:
PTHREAD_CREATE_DETACHED
:线程分离,不需要被回收,资源会在线程结束时自动释放。PTHREAD_CREATE_JOINABLE
:线程可以被其他线程通过 pthread_join 来回收(默认值)
除了直接设置attr->detachstate
成员变量外,还可以通过下列接口获取和设置:
#include <pthread.h>
int pthread_attr_getdetachstate(const pthread_attr_t * restrict attr,int *deatchstate);
int pthread_attr_setdetachstate(const pthread_attr_t * restrict attr,int *deatchstate)
注:若明确线程的退出状态,不需要被外部获取,在创建线程时,就设置为PTHREAD_CREATE_DETACHED
。
线程调度策略
线程是CPU调度的最小单元,进程是资源分配的最小单元这句话,我相信大家都很熟悉 ,但不一定理解。首先,linux 操作系统是实时操作系统,它有自己的调度策略:实时调度策略(FIFIO先到先服务算法,轮转算法)、非实时调度策略(时间片轮转)。
所谓的实时与非实时的区别,在于当有更高优先级的线程处于就绪态时,会将当前正在运行的低优先级线程中断,去执行高优先级线程。
因此应用层可以根据线程的业务需求,设置线程的调度策略,从而达到最优解。与之相关的参数有三个:
schedpolicy
:指定线程的调度策略。
取值范围是SCHED_OTHER
(默认,非实时调度策略),SCHED_FIFO
(实时调度策略),SCHED_RR
(实时调度策略)。
sched_param
:设置线程调度的参数,
仅当schedpolicy
为实时调度时,才有效。该参数主要用于设置优先级,数值越低,线程的优先级越高。
inheritsched
:指定子线程是否集成父线程的调度策略。
取值范围有两个:PTHREAD_INHERIT_SCHED
(默认,子线程继承父线程的调度策略和优先级)、PTHREAD_EXPLICIT_SCHED
(子线程使用自己定义的调度策略和优先级)。
scope
:设置CPU竞争范围。
它的取值范围有:PTHREAD_SCOPE_SYSTEM
(默认,在系统级别进行争用管理。这意味着所有线程将根据系统的调度策略进行调度,适合于高性能计算)、PTHREAD_SCOPE_PROCESS
:(在进程级别进行争用管理。这意味着线程之间的调度是局部的,适合于较小的任务)。
区别:因为实时调度存在抢占的情况。若设置为PTHREAD_SCOPE_SYSTEM
,该线程极有可能会影响其它进程的运行情况;而PTHREAD_SCOPE_PROCESS
仅抢占同进程的其它线程,这样的开销也会小一些(线程上下文切换开销小于进程上下文开销)。
线程调度策略在性能优化方面十分的重要,当我们的CPU使用较高,影响部分业务性能时,可以从这方向优化。有兴趣的,可参考案例:这个方法也许可以让你的摄像头预览更加流畅,从而加深印象。
栈属性
我们知道linux进程的虚拟内存分布大致如下图,其中栈空间是有限的。这就导致若一个线程存在大量的局部变量,或函数调用嵌套较深,则会造成栈空间不足,导致栈空间溢出。
我们可以通过设置栈的部分属性,避免相关问题。和栈属性相关的参数有stacksize
、sockaddr
、guardsize
等
-
stacksize
: 栈空间大小。linux 默认线程栈为8MB,您可以根据实际情况设置该值。 -
guardsize
:设置线程栈末尾之后,用于避免栈溢出的扩展内存的大小。如果线程的栈指针$SP
溢出到警戒区域,应用程序就会收到相关信号。 -
sockaddr
: 指定线程的栈的起始地址。当我们进程栈空间不足时,我们可以通过malloc
申请堆空间,用于线程的栈。以下为例:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define STACK_SIZE (1024 * 1024) // 1 MB
void* thread_function(void* arg) {
printf("Thread is running\n");
return NULL;
}
int main() {
pthread_t thread;
pthread_attr_t attr;
void* stack;
// 初始化线程属性
pthread_attr_init(&attr);
// 设置栈大小
pthread_attr_setstacksize(&attr, STACK_SIZE);
// 分配自定义栈
stack = malloc(STACK_SIZE);
if (stack == NULL) {
perror("Failed to allocate stack");
exit(EXIT_FAILURE);
}
// 设置自定义栈地址
pthread_attr_setstack(&attr, stack, STACK_SIZE);
// 创建线程
if (pthread_create(&thread, &attr, thread_function, NULL) != 0) {
perror("Failed to create thread");
free(stack);
exit(EXIT_FAILURE);
}
// 等待线程结束
pthread_join(thread, NULL);
// 释放栈内存
free(stack);
return 0;
}
熟练掌握线程的属性,对于我们优化代码,解决特定问题,非常有益。非常建议朋友们理解,掌握。
同步属性
与线程具有属性一样,线程的同步对象也有属性。本节介绍互斥量、读写锁、条件变量和屏障的属性,以及举例说明使用方式。
互斥量属性
我们知道互斥量类型定义为pthread_mutex_t
,定义如下:
typedef union
{
struct __pthread_mutex_s __data;
char __size[__SIZEOF_PTHREAD_MUTEX_T];
long int __align;
} pthread_mutex_t;
struct __pthread_mutex_s
{
int __lock;
unsigned int __count;
int __owner;
#ifdef __x86_64__
unsigned int __nusers;
#endif
/* KIND must stay at this position in the structure to maintain
binary compatibility with static initializers. */
int __kind;
#ifdef __x86_64__
short __spins;
short __elision;
__pthread_list_t __list;
# define __PTHREAD_MUTEX_HAVE_PREV 1
#else
unsigned int __nusers;
__extension__ union
{
struct
{
short __espins;
short __eelision;
# define __spins __elision_data.__espins
# define __elision __elision_data.__eelision
} __elision_data;
__pthread_slist_t __list;
};
# define __PTHREAD_MUTEX_HAVE_PREV 0
#endif
};
初始化方式有两种:
pthread_mutex_init
,通过该接口传入pthread_mutexattr_t
类型数据,设置互斥量的相关属性,不过大部分情况下是传入NULL;PTHREAD_MUTEX_INITIALIZER
,通过该常亮初始化互斥量。默认所有参数设置为0;该宏定义如下:
#define PTHREAD_MUTEX_INITIALIZER \
{ { __PTHREAD_MUTEX_INITIALIZER (PTHREAD_MUTEX_TIMED_NP) } }
#define __PTHREAD_MUTEX_INITIALIZER(__kind) \
0, 0, 0, 0, __kind, 0, 0, { 0, 0 }
因此,应用开发人员可以通过配置pthread_mutexattr_t
参数,修改互斥量的属性。虽然互斥量的属性较多,但是其中有各属性需要我们关注:进程共享属性、健壮属性、类型属性。
进程共享属性
我们常将互斥量应用于多线程编码中。实际上我们也可以将其应用于多进程的同步。可通过下列接口查看、修改互斥量的共享属性。
#include <pthread.h>
int phread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
int pthread_mutexattr_setpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
// 其中取值范围:PTHREAD_PROCESS_PRIVATE、PTHREAD_PROCESS_SHARED
使用示例:
/** 创建共享内存 */
int shm_fd = shm_open("/shared_mutex", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
/** 设置共享内存大小 */
ftruncate(shm_fd, sizeof(pthread_mutex_t));
/** 映射互斥锁 */
shared_mutex = mmap(NULL, SHARED_MEM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
// 初始化共享的互斥锁
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); // 设置为进程共享
pthread_mutex_init(shared_mutex, &attr);
流程:
- 创建共享内存,各进程都可以访问。
- 设置共享内存大小。
- 映射变量与共享内存,这样可以保证各进程操作的是同一个物理空间。
- 设置变量属性。
注:有些时候,即使将共享属性设置为私有(PTHREAD_PROCESS_PRIVATE
)似乎也可以实现多进程访问互斥量。但是这样存在一个隐患:互斥量被一个线程持有过长时间,其它进程等待互斥量,则会一直被阻塞。
原因分析:其它线程尝试pthread_murex_lock
获取互斥量时,若互斥量被占用,底层则是先spin100次,然后futex沉睡。当持有互斥量的线程pthread_murex_unlock
释放互斥量时,它会去唤醒其它线程。PTHREAD_PROCESS_PRIVATE
只会唤醒本进程下的线程,因此若在多进程中使用互斥量,一定要通过pthread_mutexattr_setpshared
设置共享属性。
测试示例:
//程序A
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#define SHARED_MEM_SIZE sizeof(pthread_mutex_t)
pthread_mutex_t *shared_mutex;
int main()
{
/** 创建共享内存 */
int shm_fd = shm_open("/shared_mutex", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
/** 设置共享内存大小 */
ftruncate(shm_fd, SHARED_MEM_SIZE);
/** 映射互斥锁 */
shared_mutex = mmap(NULL, SHARED_MEM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
// 初始化共享的互斥锁
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); // 设置为进程共享
pthread_mutex_init(shared_mutex, &attr);
pthread_mutex_lock(shared_mutex);
printf("enter lock\n");
sleep(10);
pthread_mutex_unlock(shared_mutex);
printf("exit lock\n");
return 0;
}
//程序B
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#define SHARED_MEM_SIZE sizeof(pthread_mutex_t)
pthread_mutex_t *shared_mutex;
int main()
{
int shm_fd = shm_open("/shared_mutex", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
shared_mutex = mmap(NULL, SHARED_MEM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
pthread_mutex_lock(shared_mutex);
printf("enter lock\n");
sleep(10);
pthread_mutex_unlock(shared_mutex);
printf("exit lock\n");
return 0;
}
健壮性
互斥量的健壮性与共享属性相关。我们知道进程之间是相互独立的,一个进程退出,正常情况下不会影响其它进程。在共享属性中我们思考这样的场景:当持有互斥量的进程终止时,并没有主动释放互斥量。其它进程如何访问互斥量呢?
健壮性的取值有两种:
PTHREAD_MUTEX_STALLED
,(默认)。它表示持有互斥量的进程终止时,不需要采取特别的动作。这种情况下,等待该互斥量解锁的应用层序会被有效地拖住。PTHREAD_MUTEX_ROBUST
,它表示线程调用pthread_mutex_lock
获取锁,而锁被另一个进程持有,但它终止时,并没有对该锁进行解锁。此时pthread_mutex_lock
返回值为EOWNERDEASD
(可恢复)或ENOTRECOVERABLE
(不可恢复)。
因此根据互斥量的健壮性定义,我们对pthread_mutex_lock
的返回值,做一些特殊处理。使用示例如下:
//进程A
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#define SHARED_MEM_SIZE sizeof(pthread_mutex_t)
pthread_mutex_t *shared_mutex;
int main()
{
/** 创建共享内存 */
int shm_fd = shm_open("/shared_mutex", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
/** 设置共享内存大小 */
ftruncate(shm_fd, sizeof(pthread_mutex_t));
/** 映射互斥锁 */
shared_mutex = mmap(NULL, SHARED_MEM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
// 初始化共享的互斥锁
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); // 设置为进程共享
pthread_mutexattr_setrobust(&attr,PTHREAD_MUTEX_ROBUST); // 设置健壮性
pthread_mutex_init(shared_mutex, &attr);
int ret = pthread_mutex_lock(shared_mutex);
if (ret == 0) {
// 正常获取锁
//do something
} else if (ret == EOWNERDEAD) {
// 互斥量处于可恢复状态
pthread_mutex_consistent(shared_mutex); // 使互斥量恢复一致性
pthread_mutex_lock(&shared_mutex); // 重新尝试锁定
// ...
pthread_mutex_unlock(shared_mutex);
} else if (ret == ENOTRECOVERABLE) {
// 互斥量处于不可恢复状态
} else {
// 其他错误处理
}
pthread_mutex_unlock(shared_mutex);
return 0;
}
//进程B
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#define SHARED_MEM_SIZE sizeof(pthread_mutex_t)
pthread_mutex_t *shared_mutex;
int main()
{
int shm_fd = shm_open("/shared_mutex", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
if (shm_fd == -1)
{
perror("shm_open failed");
exit(1);
}
shared_mutex = mmap(NULL, SHARED_MEM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
if (shared_mutex == MAP_FAILED)
{
perror("mmap failed");
exit(1);
}
int ret = pthread_mutex_lock(shared_mutex);
if (ret == 0) {
// 正常获取锁
//do something
} else if (ret == EOWNERDEAD) {
// 互斥量处于可恢复状态
pthread_mutex_consistent(shared_mutex); // 使互斥量恢复一致性
pthread_mutex_lock(&shared_mutex); // 重新尝试锁定
// ...
pthread_mutex_unlock(shared_mutex);
} else if (ret == ENOTRECOVERABLE) {
// 互斥量处于不可恢复状态
} else {
// 其他错误处理
}
pthread_mutex_unlock(shared_mutex);
return 0;
}
注:健壮性个人建议在多线程中也可以使用起来,可以避免死锁场景,提高系统稳定性。
类型属性
类型属性控制了互斥量的锁定特性。它共有四个类型。
PTHREAD_MUTEX_NOMAL
,(默认)一种标准的互斥量类型。不做任何特殊的错误检查或死锁检测。PTHREAD_MUTEX_ERRORCHECK
,提供错误检查。死锁检测(一个线程尝试锁定一个已经被它自己持有的互斥量);线程尝试锁定一个未初始化的互斥量,pthread_mutex_lock 将返回 EINVAL 错误;一个线程尝试解锁一个未被锁定的互斥量,pthread_mutex_unlock 将返回 EPERM 错误PTHREAD_MUTEX_RECURSIVE
,(递归锁)允许同一线程在互斥量解锁之前对该互斥量进行多次加锁。在解锁次数喝加锁次数不相同的情况下,不会释放锁。PTHREAD_MUTEX_DEFAULT
,此互斥量类型可以提供默认特性和行为,一般都是由系统映射到上述三个类型其中一个。
互斥量类型 | 没有解锁是重新加锁 | 不占用时解锁 | 在已解锁时解锁 |
---|---|---|---|
PTHREAD_MUTEX_NOMAL |
死锁 | 未定义 | 未定义 |
PTHREAD_MUTEX_ERRORCHECK |
返回错误 | 返回错误 | 返回错误 |
PTHREAD_MUTEX_RECURSIVE |
允许 | 返回错误 | 返回错误 |
PTHREAD_MUTEX_DEFAULT |
未定义 | 未定义 | 未定义 |
可通过下述接口设置互斥量的类型属性:
#include <pthread.h>
int pthread_mutexattr_gettype(const pthread_mutexattr_t * restrict attr, int * restrict type);
int pthread_mutexattr_settype(const pthread_mutexattr_t * restrict attr, int type);
读写锁属性
读写锁仅支持共享属性。它的使用逻辑与互斥量类似,存在两套接口初始化/反初始化pthread_rwlockattr_t
,读取和设置读写锁的进程共享属性。如下:
#include <pthread.h>
int pthread_rwlockattr_init(pthread_rwlockattr_t * attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t * attr);
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t * restrict attr, int * restrict pshared);
int pthread_rwlockattr_setpshared(const pthread_rwlockattr_t * restrict attr, int pshared);
条件变量属性
条件变量存在两个属性:进程共享属性、时钟属性。
进程共享属性与其它类似,存在两套接口初始化/反初始化pthread_condattr_t
,读取和设置条件变量的进程共享属性。如下:
#include <pthread.h>
int pthread_condattr_init(pthread_condattr_t * attr);
int pthread_condattr_destroy(pthread_condattr_t * attr);
int pthread_condattr_getpshared(const pthread_condattr_t * restrict attr, int * restrict pshared);
int pthread_condattr_setpshared(const pthread_condattr_t * restrict attr, int pshared);
时钟属性控制pthread_cond_timewait
函数的超时参数,采用的是哪一个时钟。目前有四类时钟选择。
CLOCK_REALTIME
,实时系统时间。一般应用程序中,适合大多数线程同步场景,尤其是普通的生产者-消费者问题或任务调度;适用于不需要精确时间限制的应用;CLOCK_MONOTONIC
,单调时钟时间,不受系统时间变化(如NTP时间同步)的影响,确保时间持续递增。适用于需要持续监控时间的任务,如超时控制、延迟处理等;在网络编程中,通常用来判断请求的超时情况;CLOCK_PROCESS_CPUTIME_ID
,调用进程的CPU时间。当你关心的是整个进程的性能和资源使用时,特别是在需要监控和优化进程级别的表现时。CLOCK_THREAD_CPUTIME_ID
,调用线程的CPU时间。当你需要分析单个线程的表现,特别是在复杂的多线程环境中,或者想要优化特定线程的 CPU 使用情况时。
相关接口如下:
#include <pthread.h>
int pthread_condattr_getclock(const pthread_condattr_t * restrict attr, clockid_t * restrict clock_id);
int pthread_condattr_setclock(const pthread_condattr_t * restrict attr, clockid_t clock_id);
屏障属性
屏障仅支持共享属性。它的使用逻辑与互斥量类似,存在两套接口初始化/反初始化pthread_barrierattr_t
,读取和设置屏障的进程共享属性。如下:
#include <pthread.h>
int pthread_barrierattr_init(pthread_barrierattr_t * attr);
int pthread_barrierattr_destroy(pthread_barrierattr_t * attr);
int pthread_barrierattr_getpshared(const pthread_barrierattr_t * restrict attr, int * restrict pshared);
int pthread_barrierattr_setpshared(const pthread_barrierattr_t * restrict attr, int pshared);
重入
如果一个函数在相同的时间点可以被多个线程安全调用,则称该函数是线程安全的。因此我们在多线程编码时,应该避免使用线程非安全函数或安全的使用。常见的函数有:
basename crypt getenv putenv
rand setenv getlogin getopt
strerror dirname localtime system
encrypt strtok system
线程私有数据
线程的私有数据,我们经常会听到。比如最常见的就是errno
每一个线程都独有一份;线程的状态信息等。但是我觉得有一点大家可能都误解了,线程的私有数据实际上其它线程也可以访问到。因为多线程共享进程的地址空间,各个线程可以访问任何地址空间。
只不过linux 提供了一些机制,规范了访问私有数据的流程,从而提高线程间的数据独立性,使得线程不太容易访问到其它线程的线程特定数据。是有流程大致如下:
- 创建与数据相关联的键,与析构函数。
#include <pthread.h>
int pthread_key_create(pthread_key_t *keyp, void(*destructor)(void*));
int pthread_key_delete(pthread_key_t key);
其中析构函数,是当线程退出时,若私有数据地址非空值,则会调用,入参是私有数据地址,一般是用与释放已分配的内存。(exit,exit,_Exit,abort等非正长推出,不会调用。)
-
申请私有数据内存,并初始化。
通过malloc
,初始化私有数据。 -
关联键与私有数据。
#include <pthread.h>
int pthread_key_setspecific(pthread_key_t key,const void* value);
void *pthread_key_getspecific(pthread_key_t key);
若没有私有数据值与键关联,pthread_getspcific
将返回一个空指针,可以通过这个返回值来确定是否调用pthread_key_setspecific
。
应用场景:
- 线程状态管理。每个线程可以存储与其状态相关的信息,例如任务进度,错误状态;
- 缓存和数据存储。
- 日志记录。
- 性能优化。在高性能计算中,线程私有变量可以减少锁的使用,从而提高性能。
整体而言,线程私有数据的存在,提高了程序的安全性和可维护性。
线程的取消
线程有两个属性没有包含在pthread_attr_t
结构中。分别是可取消状态和可取消类型。
可取消状态取值有两个PTHREAD_CANCEL_ENABLE
(默认)和PTHREAD_CANCEL_DISABLE
。可通过以下接口设置。
#include <pthread.h>
int ptread_setcancelstate(int state, int *oldstate);
// 把当前可取消状态设置为state,把原来的可取消状态存储在由oldstate指定的内存单元,这两步是一个原子操作。
思考:当我们执行pthread_cancel
时,线程是立刻就推出了吗?
答:并不是。在默认情况下,线程在取消请求发出以后,还是继续运行的,直到线程达到某个取消点。取消点是线程检测它是否被取消的一个位置,如果取消了,则按照请求执行。POSIX.1保证在线程中调用以下任何接口,取消点都会出现。
accept aio_suspend pthread_testcancel
sigsuspend sigtimedwait sigwait pwrite
msgsnd clock_nanosleepclose read msynC
readv sigwaitinfo sleep nanosleep
connect recv open creat
recvfrom system fcntl openat
recvmsg tcdrain pause fdatasync
poll select wait fsync
lockf pread sem_timedwait waitid
mg_receive pselect sem_wait waitpid
mq_send pthread_cond_timedwait send write
mg_timedreceive pthread_cond_wait sendmsg writev
mq_timedsend pthread_join sendto msgrcv
注:当线程中没有执行以上接口(如数学计算领域的应用程序),那么,即使调用了pthread_cancel
接口,线程也不一定会退出。此时,你可以通过pthread_testcancel
,显式的添加取消点。
若线程启动时,将可取消状态设置为PTHREAD_CANCEL_DISABLE
,即使其它线程调用了pthread_cancel
接口,也不会退出。但是会将该请求挂起,当线程状态变为PTHREAD_CANCEL_ENABLE
时,线程将在下一个取消点上对所有挂起的取消请求进行处理。
上述的流程是因为取消类型默认设置为推迟取消。(调用pthread_cancel
以后,在到达取消点之前,并不会出现真正的取消。)我们可以通过调用pthread_setcanceltype
来修改取消类型。
#include <pthread.h>
int pthread_setcanceltype(int type,int *oldtype);
//把取消类型设置为type(PTHREAD_CANCEL_DEFERREN,PTHREAD_CANCEL_ASYNCHRONOUS),原来的取消类型返回到`oldtype`指向的单元。
总结
本章详细介绍了线程更精准的控制方式,线程属性调度策略,优先级,是否分离;同步对象的属性介绍,线程退出的原理。深刻理解其中机制,相信可以帮助我们写出性能更优的程序。
若我的内容对您有所帮助,还请关注我的公众号。不定期分享干活,剖析案例,也可以一起讨论分享。
我的宗旨:
踩完您工作中的所有坑并分享给您,让你的工作无bug,人生尽是坦途