Linux多线程编程(1)——pthread概览

前言

Linux系统下的多线程遵循 =POSIX线程接口= ,称为 =pthread= 。

编写Linux下的多线程程序,需要使用头文件 =pthread.h= ,连接时需要使用 =库libpthread=.

NPTL

=Native POSIX Thread Library(NPTL)= 是一个能够使 =POSIX Threads= 编写的程序在Linux内核上更有效地运行的软件。

在Linux内核2.6出现之前进程是(最小)可调度的对象,当时的Linux不真正支持线程。但是Linux内核有一个系统调用指令clone(),这个指令产生一个调用调用的进程的复件,而且这个复件与原进程使用同一地址空间。

=LinuxThreads计划= 使用这个系统调用来提供一个内核级的线程支持。但是这个解决方法与真正的POSIX标准有一些不兼容的地方,尤其是在信号处理、进程调度和进程间同步原语方面。

要提高LinuxThreads的效应很明显需要提供内核支持以及必须重写线程函数库, 最终出现了 =NPTL项目= 。

从第3版开始NPTL是Red Hat Enterprise Linux的一部分,从Linux内核2.6开始它被纳入内核。目前它完全被结合入 =GNU C 库(glibc)= 。

NPTL的解决方法与LinuxThreads类似,内核看到的首要抽象依然是一个进程,新线程是通过clone()系统调用产生的。但是NPTL需要特殊的内核支持来解决同步的原始类型之间互相竞争的状况。在这种情况下线程必须能够入眠和再复苏。用来完成这个任务的原始类型叫做 =futex= 。

NPTL是一个所谓的1×1线程函数库。用户产生的线程与内核能够分配的对象之间的联系是一对一的。这是所有线程程序中最简单的。

pthread

线程id类型

线程id的类型是 =pthread_t=

获取线程id

通过 =pthread_self= 获取线程id:

1
2
3
#include <pthread.h>
pthread_t pthread_self(void);

比较两个线程id

通过 =pthread_equal= 来判断两个线程id是否同一个,函数原型:

1
2
#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);

创建线程

通过 =pthread_create= 创建线程:

1
2
3
4
5
6
#include <pthread.h>
int pthread_create(pthread_t *restrict thread,
const pthread_attr_t *restrict attr,
void *(*start_routine)(void *),
void *restrict arg);

参数说明:

  • 第一个参数:返回的线程id
  • 第二个参数:传递的线程属性,可以是NULL
  • 第三个参数:具体的回调函数指针,具体的线程执行体
  • 第四个参数:给函数指针的参数

在Linux 系统下面,在老的内核中,由于Thread也被看作是一种特殊、可共享地址空间和资源的进程,因此在同一个进程中创建的不同线程具有不同的 =进程ID(调用getpid获得)= 。而在新的2.6内核之中,Linux采用了 =NPTL(Native POSIX Thread Library)线程模型= ,在该线程模型下同一进程下不同线程调用getpid返回同一个PID。

终止线程

函数 =exit= , =_Exit= , =_exit= 用于中止当前 =进程= ,而非线程。

通过 =pthread_exit= 终止调用的线程:

1
2
3
#include <pthread.h>
void pthread_exit(void *value_ptr);

这个函数并不会返回值给调用者。

等待线程终止

通过 =pthread_join= 等待线程终止:

1
2
3
#include <pthread.h>
int pthread_join(pthread_t thread, void **value_ptr);

当等待的线程终止后,会把 =value_ptr= 传给 =pthread_exit=.

取消执行线程

通过 =pthread_cancel= 取消执行线程:

1
2
3
#include <pthread.h>
int pthread_cancel(pthread_t thread);

被通知 =cancel= 的线程如果自动调用 =pthread_exit(PTHREAD_CANCELLED)=, 但可以选择取消或继续执行;如果成功取消线程,则返回0,否则报错。

线程结束前自动清理

通过 =pthread_cleanup_push= 来注册线程结束时自动调用的清理函数:

1
2
3
#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void *), void *arg);

注册的函数安装顺序压入一个栈内,调用的时候就一个个的出栈。

在此函数的相同 =词法作用域= 中必须有 =pthread_cleanup_pop= 函数,此函数是调用 =栈顶函数=:

1
2
3
#include <pthread.h>
void pthread_cleanup_pop(int execute);

线程detach状态

缺省情况下,一个线程A的结束状态被保存下来直到 =pthread_join= 为该线程被调用过,也就是说即使线程A已经结束,只要没有线程B调用 pthread_join(A),A的退出状态则一直被保存。而当线程处于 =Detached状态= 之时,线程退出的时候,其资源可以立刻被回收,那么这个退出状态也丢失了。在这个状态下,无法为该线程调用pthread_join函数。

通过 =pthread_detach= 指定线程进入 =detach状态=:

1
2
3
#include <pthread.h>
int pthread_detach(pthread_t thread);

进程与线程对比

进程部分 线程部分 说明
fork pthread_create 创建
exit pthread_exit 退出
waitpid pthread_join 等待结束
atexit pthread_cleanup_push 注册退出时被调用的函数
getpid pthread_self 获得id
abort pthread_cancel 请求非正常退出

线程同步

互斥量(=mutex=)

互斥量类型

互斥量类型是 =pthread_mutex_t=

创建互斥量

通过 =pthread_mutex_init= 创建:

1
2
3
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

销毁互斥量

通过 =pthread_mutex_destroy= 来销毁:

1
2
3
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);

锁住互斥量

通过 =pthread_mutex_lock= 锁住互斥量:

1
2
3
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);

尝试去锁住一个互斥量,如果该互斥量已经被 =lock= ,则会 =阻塞= 知道该互斥量被 =unlock= 后再返回。

非阻塞锁住互斥量

通过 =pthread_mutex_trylock= 可以不阻塞的尝试锁住互斥量:

1
2
3
#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);

如果互斥量已经被锁住,则不阻塞等待解锁,而是直接返回错误。

解锁互斥量

通过 =pthread_mutex_unlock= 解锁:

1
2
3
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);

读写锁(=Reader-Writer Locks=)

同一时间,可以有多个线程获取到 =读锁=, 但只有一个线程可以获取到 =写锁=.

读写锁类型

读写锁类型是 =pthread_rwlock_t=

创建与销毁

原型:

1
2
3
4
5
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

获取读锁

原型:

1
2
3
4
5
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

获取写锁

原型:

1
2
3
4
5
#include <pthread.h>
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

释放锁

原型:

1
2
3
#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

参考

吴羽舒 wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!