Linux 多线程
Pthreads(即 POSIX Threads)是一套通用的线程库,由 POSIX 委员会定义,并被广泛应用于类 UNIX 系统,具有很好的可移植性。
Pthreads 规定了 API 来处理线程要求的大部分行为,包括创建和终止线程、等待线程完成、以及管理线程之间的交互。还有线程的锁定机制,以阻止多个线程同时尝试修改同一块数据,锁定机制包括互斥锁、条件变量。
因此,关于 Linux 多线程编程部分,主要学习三方面内容:线程管理(创建、分离、joinable 以及设置线程属性等);互斥锁(创建、销毁、lock 和 unlock 等);条件变量(conditon variable)。
线程管理
每个线程都有一个在进程中唯一的线程标识符,用一个数据类型 pthread_t 表示,该数据类型在 Linux 中是一个无符号长整型。
创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg);
参数和返回值说明:
参数 | 描述 |
---|---|
thread | 线程标识符(指向线程 ID 的指针,由该线程创建函数填入) |
attr | 线程属性(包括线程调度策略、堆栈信息、join 或 detach 状态等) |
start_routine | 线程运行函数的起始地址 |
arg | 传递给运行函数的参数 |
返回值 | |
0 | 创建成功 |
错误码 | 创建失败 |
注意:大多数 pthread 函数的返回值都是成功返回 0,失败返回错误码,但并不会设置 errno 值。
关于线程属性 attr 的管理,pthreads 也提供了一系列接口,例如 pthread_attr_init
和 pthread_attr_destroy
函数分别用来创建和销毁 pthread_attr_t 对象。
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
终止线程
线程的终止可以分为两种情况 —— 主动结束和被动终止。具体来说,当发生以下情形之一时,线程就会结束:
- 线程运行的函数 return 了,也就是线程的任务已经完成;
- 线程调用了
pthread_exit
函数; - 其他线程调用
pthread_cancel
结束这个线程; - 进程调用
exec()
或exit()
结束了; main()
函数先结束了,而且main()
本身没有调用pthread_exit
来等所有线程完成任务。
当然,一个线程结束,并不意味着它的所有信息都已经消失,后面会看到僵尸线程的问题。
下面介绍两个终止线程的函数:
void pthread_exit(void *retval);
pthread_exit()
将退出调用它的线程。retval 是由用户指定的参数, pthread_exit 完成之后可以通过这个参数获得线程的退出状态。
int pthread_cancel(pthread_t thread);
一个线程可以通过调用 pthread_cancel()
函数来请求取消同一进程中的线程,这个线程由 thread 参数指定,如果操作成功则返回 0,失败则返回对应的错误码。
对线程的阻塞
这里说的“阻塞”是指对线程的连接(join)和分离(detach),阻塞是线程之间同步的一种方法。
int pthread_join(pthread_t thread, void **retval);
pthread_join
函数会让调用它的线程等待参数 thread 指定的线程运行结束之后再运行,参数 retval 存放了其他线程的返回值。
另外,需要特别提醒,线程不能 join 自己。一个可以被 join 的线程,也仅仅可以被别的一个线程 join,如果同时有多个线程尝试 join 同一个线程时,最终结果是未知的。
上面提到过,创建一个线程时,要赋予它一定的属性,这其中就包括 joinable 或 detachable 的属性,只有被声明成 joinable 的线程,才可以被其他线程 join。POSIX 标准的最终版本指出线程应该被设置成 joinable 的。显式地设置一个线程为 joinable,需要以下四个步骤:
- 声明
pthread_attr_t
线程属性变量; - 调用
pthread_attr_init()
函数初始化线程属性变量; - 调用
pthread_attr_setdetachstate()
函数设置线程分离状态属性; - 设置完成后调用
pthread_attr_destroy()
函数释放属性使用的库资源。
示例代码:
void *thread_entry(void)
{
...
pthread_exit(NULL);
}
int main(void)
{
pthread_t tid;
pthread_attr_t attr;
/* For portability, explicitly create threads in a joinable state */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&tid, &attr, thread_entry, NULL);
...
pthread_attr_destroy(&attr);
pthread_join(tid, NULL);
pthread_exit(NULL);
}
值得注意的的是:僵尸线程(“zombie” thread)是一种已经退出了的 joinable 的线程,但仍在等待其他线程调用 pthread_join()
来 join 它,以收集它的退出信息(exit status)。如果没有其他线程 join 它的话,那么它占用的一些系统资源将不会被释放,比如堆栈。如果 main()
函数需要长时间运行,并且创建大量 joinable 的线程,就有可能出现进程堆栈不足的问题。因此,对于那些不需要 join 的线程,最好调用 pthread_detach()
将其设置为分离状态,这样它运行结束后,资源就会及时得到释放。注意,当一个线程被使用 pthread_detach 之后,它就不能再被改成 joinable 的了。
总而言之,创建的每一个线程都应该使用 pthread_join
或者 pthread_detach
其中一个,以防止僵尸线程的出现。
相关函数:
int pthread_detach(pthread_t thread);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
示例代码:
void *thread_entry(void)
{
/* Actively release resources in child thread */
pthread_detach(pthread_self());
...
pthread_exit(NULL);
}
int main(void)
{
pthread_t tid;
pthread_attr_t attr;
/* Release child thread resources in the main thread */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, thread_entry, NULL);
...
pthread_attr_destroy(&attr);
pthread_exit(NULL);
}
在主线程中释放子线程占用的资源,和在子线程中主动释放,两者选其一即可。