简介
pthread - POSIX threads 。
POSIX 是IEEE为在各种UNIX操作系统上运行软件,而定义API的一系列互相关联的标准的总称,其正式称呼为IEEE Std 1003,而国际标准名称为 ISO/IEC 9945 。
因此,对于 pthread, 有其标准,也有其实现,实现相对于标准会存在一定的出入,这也体现在后文的介绍中。
POSIX.1 中的定义
POSIX.1 为 POSIX threads 或 Pthreads 的线程编程指定了一组接口(函数、头文件)。 一个进程可以包含多个线程,所有线程都在执行同一个程序。 这些线程共享相同的全局内存(数据段和堆段),但每个线程都有自己的堆栈(自动变量)。
POSIX.1 要求线程共享一系列其他属性:
- parent process ID -
- process group ID and session ID
- controlling terminal
- user and group IDs
- open file descriptors
- record locks
- signal dispositions
- file mode creation mask
- current directory and root directory
- interval timers and POSIX timers
- nice value
- resource limits
- measurements of the consumption of CPU time and resources
除了 stack 之外,POSIX.1 还指定了以下属性对于每个线程都是不同的、独立的:
- thread ID ( 数据类型是:pthread_t)
- signal mask
- the errno variable
- alternate signal stack
- real-time scheduling policy and priority
下列的Linux特性也是每个线程独有的:
- capabilities
- CPU affinity
POSIX Thread 的 Linux 实现
随着时间的推移,Linux 上的 GNU C 库提供了两个线程实现 :
- LinuxThreads
- NPTL (Native POSIX Threads Library)
LinuxThreads
这是原始的 Pthreads 实现。 从 glibc2.4 开始,不再支持此实现。
LinuxThreads 实现在许多方面偏离了 POSIX.1 规范,因此我们暂不讨论 LinuxThreads,除非我们使用 的 glibc 是低于 2.4 的版本。
NPTL
这是现代 Pthreads 实现。 与 LinuxThreads 相比,NPTL 更符合 POSIX.1 规范的要求,并且在创建大量线程时具有更好的性能。 NPTL 从 glibc 2.3.2 开始可用,并且需要 Linux 2.6 内核中存在的功能。
在 NPTL 中:
- 一个进程中的所有线程都放在同一个线程组中。
- 线程组的所有成员共享相同的 PID。
- 不使用 manager thread。
NPTL 仍至少有一项不符合 POSIX.1:
- 线程不共享同一个 nice value
NPTL 一些不符合 POSIX.1 发生在较旧的内核:
- times(2) 和 getrusage(2) 返回的信息是每个线程的而不是进程范围的(在内核 2.6.9 中修复)。
- 线程不共享 resource limits(在内核 2.6.10 中修复)。
- 线程不共享 interval timers(在内核 2.6.12 中修复)。
- 只允许主线程使用 setsid 启动新 session (在内核 2.6.16 中修复)。
- 只允许主线程使用 setpgid 使进程成为进程组 leader (在内核 2.6.16 中修复)。
- 线程具有不同的备用信号堆栈设置。 但是,新线程的备用信号堆栈设置是从创建它的线程复制的,因此线程最初共享备用信号堆栈(在内核 2.6.16 中修复)。
请注意以下有关 NPTL 实现的进一步要点:
- 如果 stack size soft resource limit 设置为 unlimited 以外的值,则该值定义新线程的默认堆栈大小。 为了有效,这个限制必须在程序执行之前设置。
- 可以使用 ulimit -s (shell 内置命令)进行设置。
- 也可以在程序代码中使用 setrlimit 设置 RLIMIT_STACK 的 大小。
pthread 编程
头文件
#include <pthread.h>
pthread_t
typedef unsigned long int pthread_t;
- 用于声明线程ID、标识线程
pthread_create
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg)
参数:
- pthread_t :线程 ID
- pthread_attr_t :线程属性
- start_routine :函数指针,该函数的返回值类型为 void,该函数的参数类型为 void
- arg :将作为 start_routine 的参数
返回值:
- EAGAIN : 资源不足,遇到系统对资源施加的限制,无法创建另一个线程。 有许多限制可能会触发此错误:1. 已达到 RLIMIT_NPROC 软资源限制(通过 setrlimit 设置,通过 getrlimit 进行查询),该限制限制了真实用户 ID 的进程和线程数; 2. 已达到内核对进程和线程数的系统范围限制,/proc/sys/kernel/threads-max; 3. 达到了 PID 的最大量,/proc/sys/kernel/pid_max;4. 其他资源:如 打开的最大文件描述符数量,vm.max_map_count 等。
- EINVAL : attr 中的设置无效。
- EPERM : 没有权限设置attr 中指定的调度策略和参数。
注:在创建大量线程时,通常易发生 EAGAIN 错误
pthread_attr_t
typedef struct
{
int __detachstate; // 线程的分离状态
int __schedpolicy; // 线程调度策略
struct sched_param __schedparam; // 线程的调度参数
int __inheritsched; // 线程的继承性
int __scope; // 线程的作用域
size_t __guardsize; // 线程栈末尾的警戒缓冲区大小
int __stackaddr_set; // 线程的栈设置
void * __stackaddr; // 线程堆栈的位置
size_t __stacksize; // 线程栈的大小
}pthread_attr_t;
修改 pthread_attr_t 的接口:
pthread_attr_init
pthread_attr_destroy
pthread_attr_setaffinity_np(3)
pthread_attr_setdetachstate(3)
pthread_attr_setschedpolicy(3)
pthread_attr_setinheritsched(3)
pthread_attr_setschedparam(3)
pthread_attr_setscope(3)
pthread_attr_setguardsize(3)
pthread_attr_setstack(3)
pthread_attr_setstackaddr(3)
pthread_attr_setstacksize(3)
pthread_cancel
向线程发送取消请求
int pthread_cancel(pthread_t thread);
向线程发送取消请求。
目标线程是否以及何时对取消请求做出反应取决于该线程控制下的两个属性:可取消性状态(enabled (the default for new threads) or disabled)和可取消性类型(asynchronous or deferred (the default for new threads))。
- 可取消性状态:如果线程 disable 取消,则取消请求将保持排队状态,直到该线程 enable取消。如果线程 enable 取消,则根据其可取消性类型确定何时发生取消。
- 可取消性类型:asynchronous 意味着线程可以在任何时刻被取消。deferred 意味着取消将被延迟,直到线程下一次调用作为取消点的函数。
当一个取消请求被发起时, 线程中将会进入以下步骤:
-
弹出并调用取消性清理处理程序。
-
Thread-specific 的数据的析构函数将以 unspecified 的顺序被调用。
-
线程终止。
pthread_join
int pthread_join(pthread_t thread, void **retval);
pthread_join() 函数等待 thread 指定的线程终止。 如果该线程已经终止,则 pthread_join() 立即返回。 pthread_join() 指定的线程必须是 joinable 的。
如果 retval 不为 NULL,则 pthread_join() 将目标线程的退出状态(即目标线程提供给 pthread_exit 的值)复制到 retval 指向的位置。 如果目标线程被取消,则 PTHREAD_CANCELED 被放置在 retval 指向的位置。
如果多个线程同时尝试 join 同一个线程,结果是不确定的。 如果调用 pthread_join() 的线程被取消,则目标线程将保持 joinable。
pthread_detach
int pthread_detach(pthread_t thread);
pthread_detach() 函数将线程标识的线程标记为已分离。 当一个分离的线程终止时,它的资源会自动释放回系统,而无需另一个线程 join 被终止的线程。
尝试分离已分离的线程会导致未指定的行为。
返回值:
- EINVAL :thread 不是 joinable thread。
- ESRCH :找不到线程ID所标识的线程。
pthread_exit
void pthread_exit(void *retval);
终止调用线程。
pthread_exit() 函数终止调用线程并通过 retval 返回一个值(如果该线程可连接),同一进程中的另一个线程可调用 pthread_join 获取该返回值。
pthread_kill
#include <signal.h>
int pthread_kill(pthread_t thread, int sig);
向线程发送信号。
pthread_kill() 函数将信号 sig 发送到线程,该线程与调用者处于同一进程中。 信号异步定向到线程。如果 sig 为 0,则不发送信号,但仍会执行错误检查。
pthread_yield
int pthread_yield(void);
主动让出执行权,主动让出CPU时间。
pthread_self
pthread_t pthread_self(void);
获取调用者的线程ID。
pthread_setaffinity_np
int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize,
const cpu_set_t *cpuset);
int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize,
cpu_set_t *cpuset);
pthread_setaffinity_np 设置线程和CPU的亲和性。
pthread_getaffinity_np 获取线程和CPU的亲和性。
cpu_set_t 的常用接口:
#include <sched.h>
void CPU_ZERO(cpu_set_t *set); // Clears set, so that it contains no CPUs.
void CPU_SET(int cpu, cpu_set_t *set); // Add CPU cpu to set.
void CPU_CLR(int cpu, cpu_set_t *set); // Remove CPU cpu from set.
int CPU_ISSET(int cpu, cpu_set_t *set); // Test to see if CPU cpu is a member of set.
int cpu 表示第 cpu 号 cpu,可以从 /proc/cpuinfo 中查看 cpu 号(processor),通常是从 0 ~ N
更多接口见:CPU_SET
pthread_setname_np
int pthread_setname_np(pthread_t thread, const char *name);
int pthread_getname_np(pthread_t thread, char *name, size_t len);
pthread_setname_np 函数可用于为线程设置唯一名称,这对于调试多线程应用程序非常有用。
pthread_getname_np 函数可用于获取线程的名称。
pthread_mutex_t
互斥量:用于控制对资源的访问
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutexattr_t *attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 等价于 pthread_mutex_init(&mutex, NULL);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
pthread_mutexattr_t
- pthread_mutexattr_setpshared - 设置互斥锁范围设置互斥锁的强健属性
- pthread_mutexattr_getpshared - 获取互斥锁范围
- pthread_mutexattr_settype - 设置互斥锁的类型属性
- pthread_mutexattr_gettype - 获取互斥锁的类型属性
- pthread_mutexattr_setprotocol - 设置互斥锁属性的协议
- pthread_mutexattr_getprotocol - 获取互斥锁属性的协议
- pthread_mutexattr_setprioceiling - 设置互斥锁属性的优先级上限
- pthread_mutexattr_getprioceiling - 获取互斥锁属性的优先级上限
- pthread_mutex_setprioceiling - 设置互斥锁的优先级上限
- pthread_mutex_getprioceiling - 获取互斥锁的优先级上限
- pthread_mutexattr_setrobust_np - 设置互斥锁的强健属性
- pthread_mutexattr_getrobust_np - 获取互斥锁的强健属性
pthread_mutexattr_settype
- PTHREAD_MUTEX_TIMED_NP:当一个线程加锁后,其余请求锁的线程形成等待队列,在解锁后按优先级获得锁。 (默认值)
- PTHREAD_MUTEX_ADAPTIVE_NP:动作最简单的锁类型,解锁后所有线程重新竞争。
- PTHREAD_MUTEX_RECURSIVE_NP:允许同一线程对同一锁成功获得多次(递归锁)。当然也要解锁多次。其余线程在解锁时重新竞争。
- PTHREAD_MUTEX_ERRORCHECK_NP:若同一线程请求同一锁,返回EDEADLK(进行死锁检测,返回死锁的错误,避免死锁),否则与PTHREAD_MUTEX_TIMED_NP动作相同(直接死锁)。
pthread_cond_t
条件变量:用于线程间同步
int pthread_cond_init(pthread_cond_t *cond,
const pthread_condattr_t *attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 等价于 pthread_cond_init(&cond, NULL);
int pthread_cond_wait(pthread_cond_t *cond,
pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond,
pthread_mutex_t *mutex,
const struct timespec *abstime);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_destroy(pthread_cond_t *cond);
pthread_condattr_t
- pthread_condattr_setshared - 设置条件变量的进程共享属性
- pthread_condattr_getshared - 获取条件变量的进程共享属性
- pthread_condattr_setclock - 此函数用于设置pthread_cond_timewait函数使用的时钟ID
- pthread_condattr_getclock - 此函数获取可被用于pthread_cond_timedwait函数的时钟ID。
网友评论