【unix高级编程系列】线程控制

前言

在上一节中,我们介绍了线程的相关概念及使用方式。比如,如何创建线程。以及多线程编程引入的一致性问题,为了解决该问题,我们也介绍了几种同步方式:互斥量、读写锁、条件变量、自旋锁、屏障。及适合的使用场景,但也仅仅介绍了基本的使用方式。本章再深入了解其中一些机制,以及特殊场景的应用。

线程属性

我们在进行多线程编程时,线程的创建,一般采用以下方式:

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进程的虚拟内存分布大致如下图,其中栈空间是有限的。这就导致若一个线程存在大量的局部变量,或函数调用嵌套较深,则会造成栈空间不足,导致栈空间溢出。

我们可以通过设置栈的部分属性,避免相关问题。和栈属性相关的参数有stacksizesockaddrguardsize

  • 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);

流程:

  1. 创建共享内存,各进程都可以访问。
  2. 设置共享内存大小。
  3. 映射变量与共享内存,这样可以保证各进程操作的是同一个物理空间。
  4. 设置变量属性。

注:有些时候,即使将共享属性设置为私有(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;
}

注:健壮性个人建议在多线程中也可以使用起来,可以避免死锁场景,提高系统稳定性。

类型属性

类型属性控制了互斥量的锁定特性。它共有四个类型。

  1. PTHREAD_MUTEX_NOMAL,(默认)一种标准的互斥量类型。不做任何特殊的错误检查或死锁检测。
  2. PTHREAD_MUTEX_ERRORCHECK,提供错误检查。死锁检测(一个线程尝试锁定一个已经被它自己持有的互斥量);线程尝试锁定一个未初始化的互斥量,pthread_mutex_lock 将返回 EINVAL 错误;一个线程尝试解锁一个未被锁定的互斥量,pthread_mutex_unlock 将返回 EPERM 错误
  3. PTHREAD_MUTEX_RECURSIVE,(递归锁)允许同一线程在互斥量解锁之前对该互斥量进行多次加锁。在解锁次数喝加锁次数不相同的情况下,不会释放锁。
  4. 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函数的超时参数,采用的是哪一个时钟。目前有四类时钟选择。

  1. CLOCK_REALTIME,实时系统时间。一般应用程序中,适合大多数线程同步场景,尤其是普通的生产者-消费者问题或任务调度;适用于不需要精确时间限制的应用;
  2. CLOCK_MONOTONIC,单调时钟时间,不受系统时间变化(如NTP时间同步)的影响,确保时间持续递增。适用于需要持续监控时间的任务,如超时控制、延迟处理等;在网络编程中,通常用来判断请求的超时情况;
  3. CLOCK_PROCESS_CPUTIME_ID,调用进程的CPU时间。当你关心的是整个进程的性能和资源使用时,特别是在需要监控和优化进程级别的表现时。
  4. 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 提供了一些机制,规范了访问私有数据的流程,从而提高线程间的数据独立性,使得线程不太容易访问到其它线程的线程特定数据。是有流程大致如下:

  1. 创建与数据相关联的,与析构函数。
#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等非正长推出,不会调用。)

  1. 申请私有数据内存,并初始化。
    通过malloc,初始化私有数据。

  2. 关联与私有数据。

#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,人生尽是坦途

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/xieyihua1994/article/details/143044391