现代C++多线程基础 - 忆苦思甜pthread

server/2025/2/7 8:40:33/

c 进程 线程

进程

概念

进程控制块 PCB

进程控制块就是用于保存一个进程信息的结构体,又称之为PCB

  • process state 进程状态
  • process number 进程由pid唯一标记。
  • program counter PC值
  • registers 寄存器的值
  • memory limits 内存中的管理信息 (起始地址 空间 虚拟内存 …)
  • list of open files 打开的文件

PCB结构体中的部分数据

  • 调度数据:进程的状态、标志、优先级、调度策略等。

  • 时间数据:创建该进程的时间、在用户态的运行时间、在内核态的运行时间等。

  • 文件系统数据

    • umask掩码、文件描述符表等。

    • 内存数据、进程上下文、进程标识(进程号)

进程上下文

data stack heap : 支撑进程运行的环境

  • text 代码 (二进制) 只读 文字常量区、代码区
  • data 全局和静态变量数据 静态全局区
  • stack 栈 存放局部变量,函数返回值 FIFO
  • heap 堆 程序运行时的动态内存分配
  • PCB 进程控制块 包含进程信息(控制信息等)
进程号

每个进程都由一个进程号来标识,其类型为pid_t

进程号的范围:0~32767。

进程号总是唯一的,但进程号可以重用。当一个进程终止后,其进程号就可以再次使用了

# 在ubuntu中查看当前系统中所有的开启的进程
ps ajx 

PID,PPID,PGID全家桶:

#include <sys/types.h> 
#include <unistd.h> // 获取当前进程的进程号 
pid_t getpid(void); 
// 获取当前进程的父进程的进程号 
pid_t getppid(void);    
// 获取当前进程所在进程组的id
pid_t getpgid(pid_t pid); 
  • PID:当前进程的进程号。标识进程的一个非负整型数。

  • PPID:当前进程的父进程的进程号。

    任何进程(除init进程)都是由另一个进程创建,该进程称为被创建进程的父进程,对应的进程号称为父进程号(PPID)。

  • PGID:当前进程所在的组的进程组ID

    进程组是一个或多个进程的集合。他们之间相互关联,进程组可以接收同一终端的各种信号,关联的进程有一个进程组号(PGID) 。

  • COMMAND:当前进程的名字

特殊进程号:

  • 进程号为0的进程通常是调度进程,常被称为交换进程(swapper)。

  • 进程号为1的进程通常是init进程,init进程是所有进程的祖先.

常用函数

创建
fork函数 创建
pid_t fork(void); 

**功能:**在已有的进程基础上有创建一个子进程

参数:

返回值:

  • 成功: >0 返回给父进程,子进程的进程号,
  • 失败: ‐1 返回给父进程,子进程不会创建

性质:

使用fork函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间.

地址空间:

包括进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号等。

子进程所独有的只有它的进程号,计时器等。因此,使用fork函数的代价是很大的。

  • 通过fork函数的返回值来区分父子进程的独立的代码区

  • 父子进程拥有独立的地址空间

  • 子进程会复制父进程fork之前的所有内容

  • 子进程继承父进程的部分公有的区域, 如部分内核空间

fork之后,父子进程完全独立,所以不管双方怎么改变(堆区、栈区、数据区等),都不会受对方影响

子进程会继承父进程的一些公有的区域,比如如磁盘空间,内核空间文件描述符的偏移量保存在内核空间中,所以父进程改变偏移量,则子进程获取的偏移量是改变之后的

示例:

pid_t pid1,pid2;
static int b = 777;
int c = 888;pid1 = fork();
pid2 = fork();
if(pid1 < 0)
{perror("fail to fork");return -1;
}
if(pid1 = 0)  //子进程1的代码区
{printf("This is a son process1\n");printf("a = %d, b = %d, c = %d\n", a, b, c);
}
if(pid2 = 0)  //子进程2的代码区
{printf("This is a son process2\n");printf("a = %d, b = %d, c = %d\n", a, b, c);
}
else  //主进程的代码区
{ printf("This is a parent process\n");a++;b++;c++;printf("a = %d, b = %d, c = %d\n", a, b, c);
}
vfork函数 创建
#include <sys/types.h> 
#include <unistd.h>
pid_t vfork(void);

性质:功能 参数 返回值 与fork函数 几乎一样,但它们创建的子进程是有区别的

  • vfork保证子进程先运行,在它调用exec或exit之后,父进程才可能被调度运行
  • vfork并不将父进程的地址空间完全复制到子进程中,子进程和父进程共享同一块空间,因为子进程会立即调用exec(或exit),于是也就不访问该地址空间.

示例

pid_t pid = vfork(); 
int num = 1;
if(pid<0) perror("vfork"); 
if(pid==0) 
{ int i = 0; for(i=0;i<3;i++) { num++;printf("this is son process\n"); }_exit(0); 
}
else 
{ printf("this is father process\n"); printf("num=%d\n", num); sleep(1);    
}输出为this is son processthis is son processthis is son processthis is father processnum=4
挂起
sleep 挂起
#include <unistd.h> 
unsigned int sleep(unsigned int seconds); 

**功能:**进程在一定的时间内没有任何动作,称为进程的挂起(进程处于等待态)

参数:

  • seconds:指定要挂起的秒数

返回值: 若进程挂起到sec指定的时间则返回0,若有信号中断则返回剩余秒数

性质:

  • 进程挂起指定的秒数后程序并不会立即执行,系统只是将此进程切换到就绪态

  • sleep运行时进程为等待态,时间到达后会先切换到就绪态,如果代码继续运行,再切换到运行态

实例:

while(1)
{printf("morning");sleep(1);
}
wait 挂起等待任意子进程终止
#include <sys/types.h> 
#include <sys/wait.h>
pid_t wait(int *status);

功能:

  • 等待任意一个子进程终止,如果子进程终止了,此函数会回收子进程的资源
  • 调用wait函数的进程会挂起,直到它的一个子进程退出或收到一个不能被忽视的信号时才被唤醒.
  • 若调用进程没有子进程或它的子进程已经结束,该函数立即返回。
  • 接收 void exit(int status) void _exit(int status) 函数的返回值

参数:

  • status:函数返回时,参数status中保存着子进程退出时的状态信息。

  • 解析子进程的退出信息 的方法:用宏定义可以取出status中对应字段

    • WIFEXITED(status) 如果子进程是正常终止的,取出的字段值非零。

    • WEXITSTATUS(status) 返回子进程的退出状态,退出状态保存在 status 变量的 8~16 位。在用此宏前应先用宏 WIFEXITED 判断子进 程是否正常退出,正常退出才可以使用此宏。

  • 接收 void exit(int status) void _exit(int status) 函数的返回值

返回值:

  • 执行成功,则返回子进程的进程号。
  • 出错返回-1,失败原因存于 status 中。

示例:

pid_t pid; 
pid=fork(); 
if(pid<0) perror("fork"); 
if(pid==0) 
{ int i = 0; for(i=0;i<5;i++) { printf("this is son process\n"); sleep(1); }_exit(2); 
}
else 
{ int status = 0; wait(&status); if(WIFEXITED(status)!=0) { printf("son process return %d\n", WEXITSTATUS(status)); }printf("this is father process\n"); 
}
waitpid 等待指定子进程终止
#include <sys/types.h> 
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status,int options) 

**功能:**等待指定子进程终止。如果该子进程终止了,此函数会回收子进程的资源。

参数

  • pid:指定的进程或者进程组 。有以下几种类型

    pid>0:等待进程 ID 等于 pid 的子进程

    pid=0: 等待同一个进程组中的任何一个子进程,如果子进程已经加入了别的进程组,waitpid 不会等待它。

    pid=-1:等待任意一个子进程,此时 waitpid 和 wait 作用一样。

    pid<-1:等待指定进程组中的任何子进程,这个进程组的 ID 等于 pid 的绝对值。

  • status:保存子进程退出时的状态信息 (可以设置NULL)

  • options : 选项,进一步控制 waitpid 的操作

    • 0: 同 wait,阻塞父进程,等待子进程退出。

    • WNOHANG: 没有任何已经结束的子进程,则立即返回。

    • WUNTRACED: 如果子进程暂停了则此函数马上返回,并且不予以理会子进程的结束状态。(跟踪调试,很少用到)

返回值:

  • 成功: 子进程的进程号。
  • 出错返回-1,失败原因存于 errno 中。

示例:

pid_t pid; 
pid=fork(); 
if(pid < 0) perror("fork"); 
if(pid == 0) 
{ int i = 0; for(i=0;i<5;i++){ printf("this is son process\n"); sleep(1); }_exit(2); 
}else 
{ # pid:指定的进程或者进程组# status:保存子进程退出时的状态信息# 0: 同 wait,阻塞父进程,等待子进程退出int *status;waitpid(pid, status, 0); printf("this is father process\n"); 
}
其它
system函数 执行shell命令
#include <stdlib.h>
int system(const char *command);

功能:

  • system 会调用 fork 函数产生子进程
  • 子进程调用 exec 启动/bin/sh -c string 来执行参数 command 字符串所代表的命令,
  • 命令执行完后返回原调用进程。

**参数:**要执行的命令的字符串。

**返回值:**system 调用成功后会返回执行 shell 命令后的返回值。其返回值可能为 1、127 也可能为-1,故最好应再检查 status 来确认执行成功。

  • 如果 command 为 NULL,则 system()函数返回非 0,一般为 1。

  • 如果 system()在调用/bin/sh 时失败则返回 127,其它失败原因返回-1。

int status; 
status = system("ls -alh"); 
if(WIFEXITED(status)) 
{ printf("the exit status is %d \n", status); 
}else 
{ printf("abnornamal exit\n"); 
}
exec 函数族

六个 exec 函数中只有 execve 是真正意义的系统调用(内核提供的接口),其它函数都是在此基础上经过封装 的库函数.

一个进程调用 exec 后,除了进程 ID,进程还保留了下列特征不变: 父进程号 进程组号 控制终端 根目录 当前工作目录 进程信号屏蔽集 未处理信号。

功能: 在一个进程里面执行另一个程序,主要用于执行命令

参数:

path:命令或者程序的路径

l:(list)如果是带l的函数,对应的命令或者程序是通过每一个参数进行传递的,最后一个为NULL表示结束

例如:“ls”, “‐l”, NULL

execl(“/bin/ls”, “ls”, “-a”, “-l”, “-h”, NULL);

v: (vector)如果是带v的函数,对应的命令或者程序是通过一个指针数组来传递的, 指针数组的最后一个元素为NULL标识结束

char *str[] = {“ls”, “‐l”, NULL};

execv(“/bin/ls”, str);

p:(path)如果是不带p的函数,第一个参数必须传当前命令或者程序的绝对路径, 如果是带p的函数,第一个参数既可以是绝对路径,也可以是相对路径

execlp(“ls”, “ls”, “-a”, “-l”, “-h”, NULL);

e:(environment)存有环境变量字符串地址的指针数组的地址。execle 和 execve 改变的是 exec 启动的程序的环境变量(新的环境变量完全由 environment 指定),其他四个函数启动的程序则使用默认系统环境变量。

char *env[]={“USER=ME”, “GONGSI=QF”, NULL};

execle(“./test”, “test”, NULL, env);

返回值:

  • exec 函数族与一般的函数不同,exec 函数族中的函数执行成功后不会返回。
  • 只有调用失败了,它们才会返回 -1
进程的终止
退出

在 linux 下可以通过以下方式结束当前进程:

void exit(int value); 
void _exit(int value); 
exit函数
#include <stdlib.h> 
void exit(int status);exit(0);

**功能:**退出当前进程

参数:

status:退出状态,由父进程通过wait函数接收这个状态(低 8 位有效)。

  • 一般失败退出设置为非0
  • 一般成功退出设置为0

返回值: 无

_exit 函数
#include <unistd.h> 
void _exit(int value)

功能:退出当前进程

参数:

status:退出状态,由父进程通过wait函数接收这个状态(低 8 位有效)。

  • 一般失败退出设置为非0
  • 一般成功退出设置为0

返回值: 无

exit 和 exit的区别

exit 为库函数,而_exit 为系统调用.

exit库函数 刷新I/O缓冲后, 调用了 _exit 系统调用.

_exit 系统调用 不刷新I/O缓冲

exit_exit
库函数系统调用
刷新缓冲区不会刷新缓冲区
一般会使用exit
退出清理 atexit

进程在退出前可以用 atexit 函数注册退出处理函数

#include <stdlib.h> 
int atexit(void (*function)(void));

功能:注册进程正常结束前调用的函数,进程退出执行注册函数。 一个进程中可以多次调用 atexit 函数注册清理函数,正常结束前调用函数的顺序和注册时的顺序相反。

参数:

  • function:进程结束前,调用的函数指针。

返回值: 成功:0 失败:非0

性质:

  • atexit函数在进程结束时才会去执行参数对应的回调函数

  • atexit多次调用后,执行顺序与调用顺序相反 LIFO

示例:

void clear_fun1(void) 
{ printf("perform clear fun1 \n"); }
void clear_fun2(void) 
{ printf("perform clear fun2 \n"); }
int main(int argc, char *argv[]) {atexit(clear_fun1); atexit(clear_fun2);  // 执行顺序为clear_fun2 clear_fun1printf("process exit 3 sec later!!!\n"); sleep(3); return 0; 
}

tips:

复杂设计下,建议屏蔽子进程退出的信号,自行通过atexi设置进程退出时的清理工作,避免产生僵尸进程

  1. 父进程使用wait()或者waitpid()之类的函数等待子进程退出
  2. 父进程先产生一个子进程,然后子进程再产生一个孙子进程,子进程在孙子进程之前退出。
  3. 此时子进程应使用信号函数sigaction为SIGCHLD设置wait处理函数。 (只有这个使用)
signal(SIGCHLD,SIG_IGN);

线程

概念

线程是CPU调度的基本单位。包含线程id,程序计数器PC,寄存器,栈(私有)

同一进程下的线程之间分享code. data. file. 等其他操作系统资源

重量级进程有一个单线程。一个进程有多线程,它允许在同一时刻执行多个任务。

线程进程比较

每一个进程创建的时候系统会给其4G虚拟内存,3G用户空间是私有的,所以进程切换 时,用户空间也会切换,所以会增加系统开销,而一个进程中的多个线程共享一个进程的资源,所以线程切换时不用切换这些资源,效率会更高

线程的调度机制跟进程是一样的,多个线程来回切换运行。

每个进程有一个地址空间和一个控制线程(main函数就是主控线程)

调度: 线程CPU调度和分派的基本单位。

拥有资源: 进程是系统中程序执行和资源分配的基本单位。

线程自己一般不拥有资源(除了必不可少的程序计数器,一组寄存器和栈),但它可以去访问其所属进程的资源,如进程代码段,数据段以及系统资源(已打开的文件,I/O设备等)

系统开销: 同一个进程中的多个线程共享同一地址空间,因此它们之间的同步和通信的实现也变得比较容易

在进程切换时候,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置;

线程切换只需要保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作,从而能更有效地使用系统资源和提高系统的吞吐量。

并发: 不仅进程间可以并发执行,而且在一个进程中的多个线程之间也可以并发执行

  1. 解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
  2. 解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
  3. 解释三:并行是在多台处理器上同时处理多个任务。如 hadoop 分布式集群,并发是在一台处理器上“同时”处理多个任务。
多线程的用处

多任务程序的设计

一个程序可能要处理不同应用,要处理多种任务,如果开发不同的进程来处理,系统开销很大,数据共享,程序结构都不方便,这时可使用多线程编程方法。

并发程序设计

一个任务可能分成不同的步骤去完成,这些不同的步骤之间可能是松散耦合,可能通过线程的互斥,同步并发完成。这样可以为不同的任务步骤建立线程。

网络程序设计

为提高网络的利用效率,我们可能使用多线程,对每个连接用一个线程去处理

数据共享

同一个进程中的不同线程共享进程的数据空间,方便不同线程间的数据共享。

在多CPU系统中,实现真正的并行

线程属性
查询
# 观察多线程tid以及数量
ps -el
ps -elf | more
ps -elf | grep thread# 观测指定pid进程执行策略,优先级.
top -p 5004
chrt -p 35605# 强制切换pid对应进程 Real-time Scheduling 进程,并且设置priority value
chrt -f -p priority_value pid
chrt -f -p 11 35605
# 线程内获取线程id
pthread_self();# 线程竞争CPU的区间 scope
PTHREAD_SCOPE_SYSTEM   # 系统 SCS LINUX默认
PTHREAD_SCOPE_PROCESS  # 进程 PCS# Inherit scheduler 调度器
PTHREAD_INERIT_SCHED
Scheduling
# Scheduling policy调度策略 优先级
# # PR越低优先级越高# Real-time Scheduling
# # PR∈[-100,-2] 
PR=-1-priority value
PR < 0 Real-time Scheduling# Normal Scheduling
PR = -1
# priority value=0是个保留值
priority value=0# # PR越低优先级越高PR∈[0,39] 
PR>=0 Normal Scheduling 
PR=20+NI

Normal Scheduling 优先级永远低于Real-time Scheduling

  • Normal Scheduling

SCHED_OTHER时间片轮转RR SCHED_IDLE SCHED_BARCH

Priority value = 0 使用友好值NICE判断优先级 NI∈[-20,19]

PR=20+NIPR越高,优先级越低PR∈[0,39]

PR的取值也可以是rt(对应Real-time Scheduling,Real-time Scheduling中不重视PR值)

  • Real-time Scheduling

SCHED_FIFO SCHED_RR

Priority value ∈ [1,99]

1最低优先级 99 最高优先级

Real-time Scheduling中不重视PR值) NI为0

PR的取值为负PR=-1-priority value ∈[-100,-2] PR越低优先级越高

线程 POSIX

线程库POSIX

Thread Library为程序员提供创建和管理线程的API

  • POSIX Pthreadslinux下用户线程库和内核线程库
  • Windows Threads:内核线程库
  • Java Threads:依据所依赖的操作系统而定

PthreadsPOSIX标准定义的线程创建与同步API。不同的操作系统对该标准的实现不尽相同。

线程函数的程序在pthread库中,故链接时要加上参数-lpthread。

由于线程库原本不是系统本身的,所以在链接时需要手动链接库文件 gcc *.c ‐lpthread

pthread_create
pthread_t thread1;
struct Pams {int pam1, int pam2};
Pams pam = {1, 2}
void *pthread_fun1(void *arg){Pams Pam = *(Pams *)arg;printf("pam1 = %d\n", Pam->pam1);
}int res =  pthread_create(&thread1, NULL, pthread_fun1, (void *)&pam);if(res != 0) {perror("fail to pthread_create"); 
}

功能

  • 创建一个新的子线程

  • pthread_create创建的线程不与父线程在同一点开始运行,

    从指定的函数开始运行,该函数运行完后,该线程也就退出了。

  • 线程依赖进程存在,进程结束后,进程中所有的线程都会强制退出。

参数

int pthread_create(pthread_t *thread,  // 返回给thread 当前创建的线程id , 线程标识符地址。const pthread_attr_t *attr, // 设置为NULL表示以默认的属性创建 void *(*start_routine)(void *), // 线程处理函数的入口地址,如果当前函数执行完毕,则子线程也执行完毕 void *arg  // 给线程处理函数start_routine传参的参数。 
);
  • thread: 返回给thread 当前创建的线程id , 线程标识符地址。

  • attr: 线程属性结构体地址,设置为NULL表示以默认的属性创建

  • start_routine:线程处理函数的入口地址,如果当前函数执行完毕,则子线程也执行完毕

  • arg:给线程处理函数start_routine传参的参数。

返回值

  • 成功:返回 0

  • 失败:返回非 0

pthread_once

在多线程环境中,有些事仅需要执行一次。

当你写一个库时,就不能像初始化应用程序那样在main函数中初始化了。

  • 可以用静态初始化,
  • 但使用pthread_once 一次初始化会比较容易些。

尽管pthread_once()调用可能会出现在多个线程中,

  • init_routine()函数仅执行一次,
  • 究竟在哪个线程中执行是不定的,是由内核调度来决定。
int pthread_once(pthread_once_t *once_control, void (*init_routine) (void))

功能:

  • 使用once_control变量保证init_routine()函数在本进程执行序列中仅执行一次。

  • Linux Threads使用互斥锁和条件变量保证由pthread_once()指定的函数执行且仅执行一次

  • once_control变量用于表示是否执行过。

    • 初值

      • 初值为PTHREAD_ONCE_INIT(Linux Threads定义为0)
      • 如果不是PTHREAD_ONCE_INIT,pthread_once() 的行为会不正常。
    • 实际"一次性函数"的执行状态有三种:

      • NEVER(0)

      • IN_PROGRESS(1)

        所有pthread_once()都必须等待其中一个激发"已执行一次"信号,因此所有pthread_once ()都会陷入永久的等待中

      • DONE(2)

        表示该函数已执行过一次,从而所有pthread_once()都会立即返回0。

join detach

linux线程执行和windows不同,pthread有两种状态:

  • 可结合态(joinable)

    • 线程默认创建为可结合态joinable

    • 需要通过pthrad_join函数回收子线程退出的资源

      当 线程函数自己返回退出 或 pthread_exit都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当调用了pthread_join之后这些资源才会被释放。

  • 分离态(detached)

    • 使用pthread_detach函数将线程设置为分离态。

      • 既不用阻塞,

      • 也可以自动回收子线程退出的资源.

        资源在 线程函数退出 或pthread_exit自动会被释放

    • 如果子线程已经设置为分离态,就不需要再使用pthread_join

pthread_join

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);
void *pthread_fun1(void *arg) 
{int ret = *(int *)arg;return (void *)&ret;
}
pthread_create(&thread1, NULL, pthread_fun1, (void *)&imPams); 
int mRet = pthread_join(thread1, (void **)&ret);
if(mRet != 0)
{ perror("fail to pthread_join"); exit(1);
}
printf("ret_val = %d\n", *ret);

功能

  • 线程默认创建可结合态joinable,需要通过pthrad_join函数回收子线程退出的资源。
  • 阻塞等待一个子线程的退出
    • 等待子线程结束,类似wait函数 等待子进程结束
    • 并回收子线程资源。
  • 可以接收到某一个子线程调用pthread_exit时设置的退出状态值
  • 如果子线程已经设置为分离态,就不需要再使用pthread_join
  • 子线程如果要返回退出状态,可以通过返回值或者通过pthread_exit函数

参数

  • thread:指定线程的id, 被阻塞等待的线程号。
  • retval:
    • 保存子线程的退出状态值,用来存储线程退出状态的指针的地址。
    • 如果不接受则设置为NULL

返回值

  • 成功返回 0,失败返回非 0。
pthread_detach
int pthread_detach(pthread_t thread);
pthread_t thread;
pthread_create(&thread, NULL, thread_fun, NULL);
if(pthread_detach(thread) != 0) 
{perror("fail to pthread_detach"); exit(1); 
}

功能

  • 使调用线程与当前进程分离,使其成为一个独立的线程,(并行)
  • 使用pthread_detach函数将线程设置为分离态。
    • 既不用阻塞,
    • 该子线程终止时,系统将自动回收子线程的资源。

参数

  • thread:指定的子线程的id 线程号

返回值

  • 成功:返回 0,失败返回非 0。
退出/取消
pthread_exit
void pthread_exit(void *retval);
void *pthread_fun1(void *arg){static char buf[] = "This thread has quited";pthread_exit(buf);
}
pthread_t thread1;
pthread_create(&thread1, NULL, pthread_fun1, NULL);
char *str;
pthread_join(thread1, (void **)&str); 
printf("str = %s\n", str);

功能:

  • 退出正在执行的线程 退出调用线程。
  • 线程中 可在不终止整个进程的情况下停止它的控制流
    • 线程调用 pthread_exit 退出线程,从执行函数中返回。

    • 线程可以被同一进程中的其它线程取消。

  • 一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放。
  • 需要调用 pthread_join释放资源,获得返回值

参数:

  • retval:当前线程的退出状态值 存储线程退出状态的指针。
  • 这个值可以被调用pthread_join函数的线程接收到

返回值

pthread_cancel
int pthread_cancel(pthread_t thread);
pthread_t thread; 
pthread_create(&thread, NULL, pthread_fun, NULL);//通过调用pthread_cancel函数取消另一个线程 
pthread_cancel(thread); 
pthread_join(thread, NULL);

功能

  • 取消一个正在执行线程的操作。
  • pthread_cancel函数的实质是发信号给目标线程thread,使目标线程退出。
  • 发出信号就返回
    • 只是发送终止信号给目标线程,不会等待取消目标线程执行完才返回。 。
    • 发送成功并不意味着目标线程一定就会终止,线程被取消时,
  • 线程的取消属性会决定线程能否被取消以及何时被取消

参数

  • thread: 要销毁的线程的id 目标线程 ID。

返回值

  • 成功返回 0,失败返回非0 出错编号。

线程的取消属性

  • 线程的取消状态 即线程能不能被取消

    • 在 Linux 系统下,线程默认可以被取消。编程时可以在线程函数中通过pthread_setcancelstate 函数设置线程是否可以被取消。

    • pthread_setcancelstate(int state,int *old_state); 
      
    • state:

      • PTHREAD_CANCEL_DISABLE:不可以被取消
      • PTHREAD_CANCEL_ENABLE:可以被取消。
    • old_state:

      • 保存调用线程原来的可取消状态的内存地址
  • 线程取消点 即线程被取消的地方

    • 编程时可以通过 pthread_testcancel 函数设置线程的取消点。

    • void pthread_testcancel(void);
      
  • 线程的取消类型

    • 在线程能被取消的状态下,是立马被取消结束还是执行到取消点的时候被取消结束

    • pthread_setcanceltype(int type, int *oldtype); 
      
    • type:

      • PTHREAD_CANCEL_ASYNCHRONOUS:立即取消
      • PTHREAD_CANCEL_DEFERRED:不立即被取消
    • oldtype:

      • 保存调用线程原来的可取消类型的内存地址。
退出清理
pthread_cleanup_push

对清理函数进行注册

void pthread_cleanup_push(void (* routine)(void *), void *arg);

功能:

  • 线程可以注册它退出时要调用的函数,这样的函数称为线程清理处理程序(thread cleanup handler)。

    • 线程可以建立多个清理处理程序
    • 处理程序在栈中,故它们的执行顺序与它们注册时的顺序相反。
  • 注册清理函数:将清除函数压栈。满足以下情况时,依次执行。

  • 当线程执行以下动作时会调用清理函数:

    • 调用pthread_exit退出线程。

    • 响应其它线程的取消请求。

    • 用非零execute调用pthread_cleanup_pop

      无论哪种情况pthread_cleanup_pop都将删除上一次 pthread_cleanup_push 调用注册的清理处理函数。

  • 注意push与pop必须配对使用,即使pop执行不到。

参数:

  • routine:线程清理函数的指针。

  • arg:传给线程清理函数的参数。

pthread_cleanup_pop
void pthread_cleanup_pop(int execute);

功能:

  • 调用并弹出上一次注册的pthread_cleanup_push清理函数

    • 系统自动调用线程清理函数。

    • 不会退出线程。

  • 将清除函数弹栈,即删除执行过的清理函数。

参数:

  • execute:线程清理函数执行标志位。
    • 非 0,弹出清理函数,执行清理函数。
    • 0,弹出清理函数,不执行清理函数。

http://www.ppmy.cn/server/165635.html

相关文章

1992-2025年中国计算机发展状况:服务器、电脑端与移动端的演进

1992-2025年中国计算机发展状况&#xff1a;服务器、电脑端与移动端的演进 一、1992-2000年&#xff1a;市场化转型与基础积累 背景&#xff1a;改革开放深化&#xff0c;计算机产业从科研导向转向市场化&#xff0c;但核心技术仍依赖进口。 1. 服务器领域 技术基础&#xff1…

电子电器架构 --- 电子电气架构设计要求与发展方向

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…

目前,机器翻译英译日的效果远低于英译中

来源&#xff1a; https://scholar-press.com/uploads/papers/W8VFe2jz0Iz7wVY3lNvzeXeOyjAQU8gZMbXCewzD.pdf

helm-dashboard为Helm设计的缺失用户界面 - 可视化您的发布,它提供了一种基于UI的方式来查看已安装的Helm图表

一、helm-dashboard软件介绍&#xff08;文末提供下载&#xff09; Helm Dashboard是一个开源项目&#xff0c;它提供了一种基于UI的方式来查看已安装的Helm图表&#xff0c;查看它们的修订历史记录以及相应的k8s资源。它还允许用户执行简单的操作&#xff0c;如回滚到某个修订…

车型检测7种YOLOV8

车型检测7种YOLOV8&#xff0c;采用YOLOV8NANO训练&#xff0c;得到PT模型&#xff0c;转换成ONNX&#xff0c;然后OPENCV的DNN调用&#xff0c;支持C&#xff0c;python,android开发

C语言按位取反【~】详解,含原码反码补码的0基础讲解【原码反码补码严格意义上来说属于计算机组成原理的范畴,不过这也是学好编程初级阶段的必修课】

目录 概述【适合0基础看的简要描述】&#xff1a; 上述加粗下划线的内容提取版&#xff1a; 从上述概述中提取的核心知识点&#xff0c;需背诵&#xff1a; 整数【包含整数&#xff0c;负整数和0】的原码反码补码相互转换的过程图示&#xff1a; 过程详细刨析&#xff1a;…

如何使用 DeepSeek API 结合 VSCode 提升开发效率

引言 在当今的软件开发领域&#xff0c;API 的使用已经成为不可或缺的一部分。DeepSeek 是一个强大的 API 平台&#xff0c;提供了丰富的功能和数据&#xff0c;可以帮助开发者快速构建和优化应用程序。而 Visual Studio Code&#xff08;VSCode&#xff09;作为一款轻量级但功…

C++【深入 STL--list 之 迭代器与反向迭代器】

接前面的手撕list(上)文章&#xff0c;由于本人对于list的了解再一次加深。本文再次对list进行深入的分析与实现。旨在再一次梳理思路&#xff0c;修炼代码内功。 1、list 基础架构 list底层为双向带头循环链表&#xff0c;问题是如何来搭建这个list类。可以进行下面的考虑&am…