# 线程管理
# 基本操作
# pthread_create
创建一个线程
#include <pthread.h> | |
int pthread_create(pthread_t *restrict thread, | |
const pthread_attr_t *restrict attr, | |
void *(*start_routine)(void *), | |
void *restrict arg); |
Parameter:
- thread --- 返回一个唯一的线程 ID
- attr --- 可以指定线程属性对象,或者以 NULL 为缺省值
- start_routine --- 线程执行函数
- arg --- 为
start_routine
传递的参数,NULL 为缺省值
Return:
- zero --- success
- nonzero --- error code on Errors
Errors:
- EAGAIN --- 资源不足或超出创建线程数,无法创建
- EINVAL --- 无效
attr
参数属性 - EPERM --- 没有权限设置调度策略和
attr
中的参数
note:当 attr
属性为缺省 NULL 时,此线程默认为 joinable
属性。
# pthread_self
获取调用线程的 ID;可以查看一个线程自己的线程 ID,需要知道的是线程也有 ID 的,且具有唯一的线程 ID
#include <pthread.h> | |
pthread_t pthread_self(void); |
Return:
- 此函数始终成功,返回调用线程的 ID
# pthread_equal
比较俩个线程 ID,当线程 ID 相同时返回非 0,不同时返回 0
#include <pthread.h> | |
int pthread_equal(pthread_t t1, pthread_t t2); |
Parameter:
- t1 and t2:线程的 ID
Return:
- nonzero --- 俩线程 ID 相等
- zero --- 俩线程 ID 不同
# pthread_join
线程可以是 joinable 或 detached 的。 如果一个线程是 joinable,那么另一个线程可以调用 pthread_join()
等待该线程终止(挂起当前线程,阻塞式等待线程结束,如果线程已结束则立即返回)
#include <pthread.h> | |
int pthread_join(pthread_t thread, void **retval); |
Parameter:
- thread --- 需要终止的线程 ID
- retval --- 退出的状态返回值,用于提供给
pthread_exit()
Return:
- zero --- success
- nonzero --- error code on Errors
Errors:
- EDEADLK --- 检测到死锁或自身调用
- EINVAL --- 该线程非 joinable 线程或另一个线程已经在等待加入这个线程
- ESRCH --- 传入的
thread
参数是不存在的线程 ID
# pthread_detach
用于把 joinable 线程转变为 detached 线程,一旦把 joinable 线程被设置为 detached 的,那么这个线程就不能被 pthread_join()
调用
#include <pthread.h> | |
int pthread_detach(pthread_t thread); |
Parameter:
- thread --- 需要修改的线程 ID
Return:
- zero --- success
- nonzero --- error code on Errors
Errors:
- EINVAL --- 该线程非 joinable 线程
- ESRCH --- 传入的
thread
参数是不存在的线程 ID
# pthread_exit
终止线程,也就是终止自己的执行
#include <pthread.h> | |
void pthread_exit(void *retval); |
Parameter:
- retval --- 由
pthread_join()
提供参数,或以 NULL 为缺省值自身调用结束
# 参数配置
# pthread_attr_t
pthread_attr_t
数据类型,以下函数都调用该结构属性:
typedef struct | |
{ | |
int detachstate; // 线程的分离状态 | |
int schedpolicy; // 线程的调度策略 | |
structsched_param schedparam; // 线程的优先级 | |
int inheritsched; // 线程的继承性 | |
int scope; // 线程的作用域 | |
size_t guardsize; // 线程栈的警戒缓冲区大小 | |
int stackaddr_set; // 线程的栈设置 | |
void* stackaddr; // 线程栈的地址 | |
size_t stacksize; // 线程栈的大小 | |
} pthread_attr_t; |
detachstate:表示新线程是否与进程中其他线程脱离同步, 如果设置为
PTHREAD_CREATE_DETACHED
则新线程不能用pthread_join()
来同步,且在退出时自行释放所占用的资源;缺省为PTHREAD_CREATE_JOINABLE
状态。这个属性也可以在线程创建并运行以后用
pthread_detach()
来设置,而一旦设置为PTHREAD_CREATE_DETACH
状态(不论是创建时设置还是运行时设置)则不能再恢复到PTHREAD_CREATE_JOINABLE
状态。schedpolicy:表示新线程的调度策略,主要包括
SCHED_OTHER
(正常、非实时)、SCHED_RR
(实时、轮转法)和SCHED_FIFO
(实时、先入先出)三种,缺省为SCHED_OTHER
,后两种调度策略仅对超级用户有效;运行时可以用过pthread_setschedparam()
来改变。schedparam:一个
struct sched_param
结构,目前仅有一个sched_priority
整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR
或SCHED_FIFO
)时才有效,并可以在运行时通过pthread_setschedparam()
函数来改变,缺省为 0。inheritsched:有两种值可供选择:
PTHREAD_EXPLICIT_SCHED
和PTHREAD_INHERIT_SCHED
,前者表示新线程使用显式指定调度策略和调度参数(即由schedpolicy
和schedparam
属性确定),而后者表示继承调用者线程的调度策略和调度参数。缺省为PTHREAD_EXPLICIT_SCHED
。scope:表示线程间竞争 CPU 的范围,也就是说线程优先级的有效范围。POSIX 的标准中定义了两个值:
PTHREAD_SCOPE_SYSTEM
和PTHREAD_SCOPE_PROCESS
,前者表示与系统中所有线程一起竞争资源(CPU 等),后者表示仅与同一进程中的线程竞争资源(CPU 等)。目前 LinuxThreads 仅实现了PTHREAD_SCOPE_SYSTEM
值。guardsize:控制着线程栈保护区大小,以避免栈溢出,默认设置为系统的页大小。
可以把
guardsize
线程属性设为 0,从而不允许属性的这种特征行为发生:在这种情况下不会提供警戒缓存区。同样地,如果对线程属性stackaddr
作了修改,系统就会假设我们会自己管理栈,并使警戒栈缓冲区机制无效,等同于把guardsize
线程属性设为 0。如果
guardsize
大于零,则会为每个使用 attr 创建的线程提供大小至少为guardsize
字节的溢出保护区。stackaddr_set:
stackaddr:该属性设置新线程所用栈的栈地址。
stacksize:该属性设置新线程所用栈的栈大小。
# pthread_attr_init
线程属性初始化
#include <pthread.h> | |
int pthread_attr_init(pthread_attr_t *attr); |
# pthread_attr_destroy
销毁线程属性对象
#include <pthread.h> | |
int pthread_attr_destroy(pthread_attr_t *attr); |
# 设置 / 获取线程的分离状态
该属性值设置及获取由如下两个函数进行:
#include <pthread.h> | |
// detachstate:PTHREAD_CREATE_DETACHED or PTHREAD_CREATE_JOINABLE | |
int pthread_attr_setdetachstate(pthread_attr_t *attr, | |
int detachstate); | |
int pthread_attr_getdetachstate(const pthread_attr_t *attr, | |
int *detachstate); |
# 设置 / 获取线程的调度策略
该属性值设置及获取由如下两个函数进行:
#include <pthread.h> | |
// policy:SCHED_FIFO, SCHED_RR or SCHED_OTHER | |
int pthread_attr_setschedpolicy(pthread_attr_t *attr, | |
int policy); | |
int pthread_attr_getschedpolicy(const pthread_attr_t *restrict attr, | |
int *restrict policy); |
# 设置 / 获取线程的优先级
该属性值设置及获取由如下两个函数进行:
#include <pthread.h> | |
int pthread_attr_setschedparam(pthread_attr_t *restrict attr, | |
const struct sched_param *restrict param); | |
int pthread_attr_getschedparam(const pthread_attr_t *restrict attr, | |
struct sched_param *restrict param); |
所用系统支持的优先级最大最小值可以使用如下函数查询:
#include <sched.h> | |
int sched_get_priority_max(int policy); | |
int sched_get_priority_min(int policy); |
note:
该属性值仅当调度策略为实时(即 SCHED_RR 或 SCHED_FIFO )时才有效,因此一般使用以下函数设置优先级:
#include <pthread.h> | |
struct sched_param sched; | |
sched.sched_priority = -1; | |
pthread_t pidtake; | |
pthread_create(&pidtake, NULL, thread_take, NULL); | |
pthread_setschedparam(pidtake, SCHED_RR, &sched); |
# 设置 / 获取线程的继承性
该属性值设置及获取由如下两个函数进行:
#include <pthread.h> | |
// inheritsched:PTHREAD_INHERIT_SCHED or PTHREAD_EXPLICIT_SCHED | |
int pthread_attr_setinheritsched(pthread_attr_t *attr, | |
int inheritsched); | |
int pthread_attr_getinheritsched(const pthread_attr_t *restrict attr, | |
int *restrict inheritsched); |
# 设置 / 获取线程的作用域
该属性值设置及获取由如下两个函数进行:
#include <pthread.h> | |
// scope:PTHREAD_SCOPE_SYSTEM or PTHREAD_SCOPE_PROCESS | |
int pthread_attr_setscope(pthread_attr_t *attr, | |
int scope); | |
int pthread_attr_getscope(const pthread_attr_t *restrict attr, | |
int *restrict scope); |
# 设置 / 获取线程栈保护区大小
该属性值设置及获取由如下两个函数进行:
#include <pthread.h> | |
int pthread_attr_setguardsize(pthread_attr_t *attr, | |
size_t guardsize); | |
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr, | |
size_t *restrict guardsize); |
# 设置 / 获取线程的内存区域
该属性值设置及获取由如下两个函数进行:
#include <pthread.h> | |
int pthread_attr_setstackaddr(pthread_attr_t *attr, | |
void *stackaddr); | |
int pthread_attr_getstackaddr(const pthread_attr_t *restrict attr, | |
void **restrict stackaddr); |
# 设置 / 获取线程的栈大小
该属性值设置及获取由如下两个函数进行:
#include <pthread.h> | |
int pthread_attr_setstacksize(pthread_attr_t *attr, | |
size_t stacksize); | |
int pthread_attr_getstacksize(const pthread_attr_t *restrict attr, | |
size_t *restrict stacksize); |
# 信号量
# sem_init
初始化一个匿名信号量
#include <semaphore.h> | |
int sem_init(sem_t *sem, int pshared, unsigned int value); |
Parameter:
- sem --- 返回一个信号量
- pshared --- 为 0 表示该信号量在某个进程中的多个线程之间共享,该信号量应当能够被所有的线程访问,例如是一个全局变量或者是在堆上动态分配的变量;若不为 0 ,表示在进程间共享,那么该信号量应该位于共享内存的区域中
- value --- 指定信号量的初始值
Return:
- zero --- success
- nonzero(-1) --- error
# sem_wait / sem_trywait / sem_timedwait
锁定给定的信号量
对于 sem_wait
,如果信号量的值 sval
大于 0 , sem_wait
会对 sval
减 1 ,函数立即返回。如果 sval
为 0 , sem_wait
将阻塞调用者(某个进程或线程),直到 sval
重新大于 0 或者调用者被 Linux 系统中的系统调用 signal()
函数中断 。
对于 sem_trywait
,如果对 sval
的减 1 操作不能完成, sem_trywait
不会阻塞调用者,而是返回一个 error
,并设置 errno
为 EAGAIN
。
对于 sem_timedwait
,如果 sval
为 0,则阻塞等待,当阻塞时长超过 abs_timeout
返回失败( errno
设置为 ETIMEDOUT
) 。
#include <semaphore.h> | |
struct timespec { | |
time_t tv_sec; /* Seconds */ | |
long tv_nsec; /* Nanoseconds [0 .. 999999999] */ | |
}; | |
int sem_wait(sem_t *sem); | |
int sem_trywait(sem_t *sem); | |
int sem_timedwait(sem_t *restrict sem, | |
const struct timespec *restrict abs_timeout); |
Parameter:
- sem --- 需加锁的信号量
- abs_timeout ---
struct timespec
结构的超时时间值
Return:
- zero --- success
- nonzero(-1) --- error
# sem_post
释放给定的信号量,对信号量的值 sem_val
进行加 1 操作,如果该信号量的值最终大于 0 ,则唤醒被 semwait()
阻塞的另一个进程或线程,并重新锁定该信号量
#include <semaphore.h> | |
int sem_post(sem_t *sem); |
Parameter:
- sem --- 需解锁的信号量
Return:
- zero --- success
- nonzero(-1) --- error
# sem_getvalue
获取给定信号量的值
#include <semaphore.h> | |
int sem_getvalue(sem_t *restrict sem, int *restrict sval); |
Parameter:
- sem --- 需获取的信号量
- sval --- 返回的当前信号量的值
Return:
- zero --- success
- nonzero(-1) --- error
# sem_destroy
销毁一个匿名信号量
#include <semaphore.h> | |
int sem_destroy(sem_t *sem); |
Parameter:
- sem --- 需销毁的信号量
Return:
- zero --- success
- nonzero(-1) --- error
# 互斥锁
在多线程编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。 每个对象都对应于一个可称为 "互斥锁" 的标记,这个标记用来保证在任一时刻, 只能有一个线程访问该对象。互斥锁也可以叫线程锁。
# 基本操作
# PTHREAD_MUTEX_INITIALIZER
静态初始化互斥锁
# define PTHREAD_MUTEX_INITIALIZER \ | |
{ { 0, 0, 0, 0, 0, 0, { 0, 0 } } } |
eg:
pthread_mutex_t s_mutex = PTHREAD_MUTEX_INITIALIZER; |
# pthread_mutex_init
动态初始化互斥锁
#include <pthread.h> | |
int pthread_mutex_init(pthread_mutex_t *mutex, | |
const pthread_mutexattr_t *mutexattr); |
Parameter:
- mutex --- 返回一个唯一的互斥锁 ID
- mutexattr --- 可以指定互斥锁属性对象,或者以 NULL 为缺省值
Return:
- zero --- 此函数始终成功
# pthread_mutex_lock
锁定给定的互斥锁,阻塞调用。如果这个互斥锁此时正在被其它线程占用, 那么 pthread_mutex_lock()
调用会进入到这个互斥锁的等待队列中,并会进入阻塞状态, 直到拿到该锁之后才会返回。
#include <pthread.h> | |
int pthread_mutex_lock(pthread_mutex_t *mutex); |
Parameter:
- mutex --- 需加锁的互斥锁 ID
Return:
- zero --- success
- nonzero --- error code on Errors
Errors:
- EINVAL --- 未正确初始化
- EDEADLK --- 互斥锁已经被调用线程锁定(仅
PTHREAD_MUTEX_ERRORCHECK
类型互斥锁)
# pthread_mutex_trylock
锁定给定的互斥锁,非阻塞调用。当请求的锁正在被占用的时候, 不会进入阻塞状态,而是立刻返回,并返回一个错误代码 EBUSY
,意思是说, 有其它线程正在使用这个锁。
#include <pthread.h> | |
int pthread_mutex_trylock(pthread_mutex_t *mutex); |
Parameter:
- mutex --- 需加锁的互斥锁 ID
Return:
- zero --- success
- nonzero --- error code on Errors
Errors:
EBUSY --- 互斥锁无法获取,因为目前已被锁定占用
EINVAL --- 未正确初始化
# pthread_mutex_unlock
释放给定的互斥锁
#include <pthread.h> | |
int pthread_mutex_unlock(pthread_mutex_t *mutex); |
Parameter:
- mutex --- 需解锁的互斥锁 ID
Return:
- zero --- success
- nonzero --- error code on Errors
Errors:
- EINVAL --- 未正确初始化
- EPERM --- 调用线程未拥有互斥锁(仅
PTHREAD_MUTEX_ERRORCHECK
类型互斥锁)
# pthread_mutex_destroy
销毁由 pthread_mutex_init
动态初始化的互斥锁,释放它可能持有的资源
#include <pthread.h> | |
int pthread_mutex_destroy(pthread_mutex_t *mutex); |
Parameter:
- mutex --- 需销毁的互斥锁 ID
Return:
- zero --- success
- nonzero --- error code on Errors
Errors:
- EBUSY --- 互斥锁目前已被锁定占用,无法进行销毁
# 参数配置
# pthread_mutexattr_t
pthread_mutexattr_t
数据类型,以下函数都调用该结构属性:
/* Data structures for mutex handling. The structure of the attribute | |
type is not exposed on purpose. */ | |
typedef union | |
{ | |
struct __pthread_mutex_s | |
{ | |
int __lock; | |
unsigned int __count; | |
int __owner; | |
#if __WORDSIZE == 64 | |
unsigned int __nusers; | |
#endif | |
/* KIND must stay at this position in the structure to maintain | |
binary compatibility. */ | |
int __kind; | |
#if __WORDSIZE == 64 | |
int __spins; | |
__pthread_list_t __list; | |
# define __PTHREAD_MUTEX_HAVE_PREV 1 | |
#else | |
unsigned int __nusers; | |
__extension__ union | |
{ | |
int __spins; | |
__pthread_slist_t __list; | |
}; | |
#endif | |
} __data; | |
char __size[__SIZEOF_PTHREAD_MUTEX_T]; | |
long int __align; | |
} pthread_mutex_t; |
# pthread_mutexattr_init
互斥锁属性初始化
#include <pthread.h> | |
int pthread_mutexattr_init(pthread_mutexattr_t *attr); |
# pthread_mutexattr_destroy
销毁互斥锁属性对象
#include <pthread.h> | |
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); |
# pthread_mutexattr_settype
设置互斥锁类型属性
#include <pthread.h> | |
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type); |
如果互斥锁类型为 PTHREAD_MUTEX_NORMAL
,则不提供死锁检测。尝试重新锁定互斥锁会导致死锁。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或未锁定,则将产生不确定的行为。
如果互斥锁类型为 PTHREAD_MUTEX_ERRORCHECK
,则会提供错误检查。如果某个线程尝试重新锁定的互斥锁已经由该线程锁定,则将返回错误。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。
如果互斥锁类型为 PTHREAD_MUTEX_RECURSIVE
,则该互斥锁会保留锁定计数这一概念。线程首次成功获取互斥锁时,锁定计数会设置为 1。线程每重新锁定该互斥锁一次,锁定计数就增加 1。线程每解除锁定该互斥锁一次,锁定计数就减小 1。 锁定计数达到 0 时,该互斥锁即可供其他线程获取。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。
如果互斥锁类型是 PTHREAD_MUTEX_DEFAULT
,则尝试以递归方式锁定该互斥锁将产生不确定的行为。对于不是由调用线程锁定的互斥锁,如果尝试解除对它的锁定,则会产生不确定的行为。如果尝试解除锁定尚未锁定的互斥锁,则会产生不确定的行为。