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 栈
存放局部变量,函数返回值 FIFOheap 堆
程序运行时的动态内存分配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设置进程退出时的清理工作,避免产生僵尸进程。
- 父进程使用wait()或者waitpid()之类的函数等待子进程退出
- 父进程先产生一个子进程,然后子进程再产生一个孙子进程,子进程在孙子进程之前退出。
- 此时子进程应使用信号函数sigaction为SIGCHLD设置wait处理函数。 (只有这个使用)
signal(SIGCHLD,SIG_IGN);
线程
概念
线程是CPU调度的基本单位。包含线程id,程序计数器PC,寄存器,栈(私有)
同一进程下的线程之间分享code. data. file. 等其他操作系统资源
重量级进程有一个单线程。一个进程有多线程,它允许在同一时刻执行多个任务。
线程进程比较
每一个进程创建的时候系统会给其4G虚拟内存,3G用户空间是私有的,所以进程切换 时,用户空间也会切换,所以会增加系统开销,而一个进程中的多个线程共享一个进程的资源,所以线程切换时不用切换这些资源,效率会更高
线程的调度机制跟进程是一样的,多个线程来回切换运行。
每个进程有一个地址空间和一个控制线程(main函数就是主控线程)
调度: 线程是CPU调度和分派的基本单位。
拥有资源: 进程是系统中程序执行和资源分配的基本单位。
线程自己一般不拥有资源(除了必不可少的程序计数器,一组寄存器和栈),但它可以去访问其所属进程的资源,如进程代码段,数据段以及系统资源(已打开的文件,I/O设备等)
系统开销: 同一个进程中的多个线程可共享同一地址空间,因此它们之间的同步和通信的实现也变得比较容易
在进程切换时候,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置;
线程切换只需要保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作,从而能更有效地使用系统资源和提高系统的吞吐量。
并发: 不仅进程间可以并发执行,而且在一个进程中的多个线程之间也可以并发执行。
- 解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
- 解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
- 解释三:并行是在多台处理器上同时处理多个任务。如 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
时间片轮转RRSCHED_IDLE
SCHED_BARCH
Priority value = 0 使用友好值NICE判断优先级 NI∈[-20,19]
PR=20+NI
PR越高,优先级越低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 Pthreads
:linux下用户线程库和内核线程库Windows Threads
:内核线程库Java Threads
:依据所依赖的操作系统而定
Pthreads
是POSIX
标准定义的线程创建与同步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,弹出清理函数,不执行清理函数。