多线程
PART1——线程相关概念
线程时参与系统调度的最小单位。被包含在进程之中,是进程中的实际运行单位。一个进程可以创建多个线程,多个线程实现并发运行,每个线程执行不同的任务。
- 线程时最基本的运行单位,而进程不能运行,真正运行的是进程中的线程
- 可以认为进程是一个容器,包含了线程运行所需的数据结构、环境变量
- 其他线程都是由主线程创建
- 主线程通常会在最后结束运行,执行各种清理工作
- 同一个进程中的多个线程将共享进程中的全部系统资源,如虚拟地址空间、文件描述符和信号处理等
- 同一个进程的多个线程之间可以并发执行
1.1 进程与线程
- 进程间切换开销大
- 进程间通信麻烦
- 进程创建速度比线程慢
- 线程编程难度高
1.2 并行与并发
- 串行
- 并行
- 并发
1.3 线程ID
-
每个线程都有其对应的标识
-
线程ID只有在它所属的进程上下文才有意义
-
通过
pthread_t pthread_self(void)
获取自己的线程ID -
通过
int pthread_equal(pthread_t t1,pthread_t t2)
检查两个线程ID是否相等
PART2——线程使用
2.1 创建线程
-
应用程序可以通过
int pthread_creat(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg)
来创建一个线程-
新创建的线程的线程ID会保存在参数thread所指向的内存中
-
线程的属性
-
新创建的线程从start_routine函数开始运行
-
传递给送start_routine函数的参数
-
创建成功是返回0
-
-
应用程序中使用
pthread_creat
创建线程后,编译是要指定-lpthread
2.2 终止线程
-
线程的
start_routine
函数执行return语句并返回指定值,返回值就是线程的退出码 -
线程调用
void pthread_exit(void *retval)
函数来终止调用它的线程 -
调用
pthread_cancel()
取消线程
2.3 回收线程
-
应用程序通过调用
int pthread_join(pthread_t thread,void **retval)
函数来阻塞等待线程的终止-
thread
等待指定线程终止 -
**retval
如果被pthread_exit()
终止,则将其退出状态赋值给retval,如果被pthread_cancel()
终止,则将PTHREAD_CANCELED
放在retval中
-
-
线程之间可以互相进行
pthread_join()
,线程之间是对等的 -
不能以非阻塞的方式调用
pthread_join()
-
可以通过
int pthread_detach(pthread_t thread)
将指定线程进程分离,此时线程终止时系统就能够自动回收线程资源并将其移除-
设置为分离线程后,便不能在恢复到之前的状态,此过程不可逆
-
设置为分离线程后,便不能在用
pthread_join()
回收资源,会出错
-
-
可以通过
void pthread_cleanup_push(void(*routine)(void *),void *arg)
void pthread_cleanup_pop(int execute)
分别添加线程清理函数和移除线程的清理函数,成对出现
-
当线程调用
pthread_exit()
退出时,添加的清理函数才会被执行 -
线程响应
pthread_cancel()
时,添加的清理函数才会被执行 -
用非零参数调用
pthread_cleanup_pop()
时,添加的清理函数才会被执行,参数为0时,清理函数不会执行,只会将最顶层的函数移除
-
2.4 取消线程
-
通过
int pthread_cancel(pthread_t thread)
要求一个线程立即终止/退出 -
pthread_cancel()
调用后,不会等待目标线程的退出,直接返回 -
通过
int pthread_setcancelstate(int state,int *olestate)
设置线程取消的状态-
state
可以设置为:-
PTHREAD_CANCEL_ENABLE
线程可以被取消,默认值 -
PTHREAD_CANCEL_DISABLE
线程不可被取消,会将请求挂起,直到取消状态变为PTHREAD_CANCEL_ENABLE
-
-
olestate
,将线程之前的状态保存在此,如果不关心返回状态则可以设置为NULL
-
-
通过
int pthread_setcanceltype(int type,int *oldtype)
设置线程取消的类型-
type
可以设置为:-
PTHREAD_CANCEL_DEFERRED
当取消请求到来时,线程继续运行,直到运行到某个取消点,默认值 -
PTHREAD_CANCEL_ASYNCHRONOUS
可能在任何时间点取消线程
-
-
-
可以通过
man 7 pthreads
查看可以作为取消点的函数 -
可以通过
void pthread_testcancel(void)
作为取消点
2.5 线程属性
2.5.1 使用
-
如果使用默认属性则把
pthread_attr_t *attr
设置为NULL即可 -
如果要设置线程属性需要通过
int pthread_attr_init(pthread_attr_t *attr)
对属性对象进行初始化操作,并在不需要时通过int pthread_attr_destory(pthread_attr_t *attr
对属性对象进行销毁
2.5.2 线程栈属性
-
通过
int pthread_attr_setstack(pthread_attr_t *attr,void *stackaddr,size_t stacksize)
对栈的起始地址和栈大小进行设置 -
通过
int pthread_attr_getstack(const pthread_attr_t *attr,void **stackaddr,size_t *stacksize)
获取栈信息 -
获取栈代大小
pthread_attr_getstacksize()
-
设置栈大小
pthread_attr_gsetstacksize()
-
获取栈地址
pthread_attr_getstackaddr()
-
设置栈地址
pthread_attr_setstackaddr()
2.5.3 分离状态属性
-
设置分离属性
int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate)
-
获取分离属性
int pthread_attr_getdetachstate(const pthread_attr_t *attr,int *detachstate)
-
detachstate
的值可以为:-
PTHREAD_CREATE_DETACHED
新建线程直接处于分离属性,系统自动回收资源 -
PTHREAD_CREATE_JOINABLE
默认值,正常启动,可以被其他线程获取终止状态信息
-
2.6 线程安全
2.6.1 可重入函数
-
如果一个函数被同一个进程的多个不同的执行流同时调用,诶此函数调用总是能产生正确的结果,这样的函数就是可重入函数
-
可重入函数分为:
-
绝对的可重入函数(函数内部仅仅操作函数内部定义的局部变量,除了使用栈上的变量以外不依赖任何环境变量,多个副本同时运行时,使用的分离的栈)
-
带条件的可重入函数
-
-
使用
man 3 ctime
查询时,MT-Safe
指的是多线程安全,MT-Unsafe
指的是线程不安全
2.6.2 线程安全函数
-
一个函数被多个线程同时调用时,总会一直产生正确的结果,这样的函数被称为线程安全函数
-
可重入函数一定是线程安全函数,线程安全函数不一定是可重入函数
-
可以通过
man 7 pthreads
查询线程安全函数
2.6.3 一次性初始化
-
应用程序如果需要只运行一次的函数,则可以通过
int pthread_once(pthread_once_t *once_control,void(*init_routine)(void))
使init_routine
函数只运行一次通常
pthread_once_t once_control = PTHREAD_ONCE_INIT;
-
具体在哪个线程中执行是不确定的,有内核调度决定
2.6.4 线程特有数据
-
为每一个调用线程分配属于该线程的私有数据区,为每一个调用线程分别维护一份变量的副本
-
针对一些非线程安全的函数,使用线程特有数据将其变成线程安全函数