pthread 是 POSIX Thread 的缩写,是一套标准线程库,大部分的 *nix 系统都实现了它,在 windows 上也有相应的实现。这里的实验环境为 debian 6.0。
并行和并发(参考资料 [1])
并行的英文是 parallelism,并发的英文是 concurrency,两者表达的意思并不一样:并行是指多条指令 同时 执行(这里的“同时”指时间上的同时),而并发是指多条指令可能以任意顺序执行(也有可能是并行执行)。
第一个 pthread 程序
#include <stdio.h>
#include <pthread.h>
static void* print_tid(void* arg)
{
printf("thread id: %d\n", *((int*)arg));
return arg;
}
#define THREAD_NR 10
int main()
{
int status;
int i, tid[THREAD_NR];
pthread_t pt[THREAD_NR];
for (i = 0; i < THREAD_NR; ++i) {
tid[i] = i;
status = pthread_create(&pt[i], NULL, print_tid, &tid[i]);
if (status != 0)
fprintf(stderr, "create thread %d fail.\n", i);
}
return 0;
}
使用 gcc 编译的时候要加上“-lpthread”。
pthread_create() 函数创建一个线程。manpage 中,pthread_create() 的函数原型为:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
第一个参数是线程的唯一标识;第二个参数设置线程的属性,如果为 NULL 则使用默认的属性;第三个参数是一个函数指针,指向线程要执行的回调函数;第四个参数是传递给回调函数的参数。
运行几次这个程序,从输出结果看到,线程打印出了一些值,这些值的顺序是乱序的,说明操作系统并一定按照线程创建的顺序执行线程;同时也看到有些线程好像并没有被执行,有些线程打印的值并不是传递进去的值,这是因为创建完所有线程后 main() 函数就退出了,这意味着主线程结束,而由主线程创建的线程也会随之结束,尽管其中有些线程还没来得及运行。
如果想让主线程等到所有线程都运行完后再退出,可以使用 pthread_join()。pthread_join() 等待线程结束并且回收线程的资源:
int pthread_join(pthread_t thread, void **retval);
第一个参数是由 pthread_create() 创建线程时分配的 thread id,第二个参数是一个二级指针,如果 retval 为空则忽略回调函数的返回值,否则回调函数的返回值保存在 *retval 中:
#include <stdio.h>
#include <pthread.h>
static void* print_tid(void* arg)
{
printf("thread id: %d\n", *((int*)arg));
return arg;
}
#define THREAD_NR 10
int main()
{
int status;
int i, tid[THREAD_NR];
pthread_t pt[THREAD_NR];
for (i = 0; i < THREAD_NR; ++i) {
tid[i] = i;
status = pthread_create(&pt[i], NULL, print_tid, &tid[i]);
if (status != 0)
fprintf(stderr, "create thread %d fail.\n", i);
}
for (i = 0; i < THREAD_NR; ++i) {
status = pthread_join(pt[i], NULL);
if (status != 0)
fprintf(stderr, "join thread %d fail.\n", i);
}
return 0;
}
如果仅仅是需要执行线程而不关心线程的返回值,可以使用 pthread_detach():
int pthread_detach(pthread_t thread);
这样操作系统在线程执行完后会回收线程的资源,线程的返回值,状态等信息都被忽略。但是这样又会出现第一个程序的问题,就是如果主线程结束了还没来得及执行的线程也会被杀死,这就需要另外的方法来保证主线程等到所有线程都执行完后再结束。
对同一个线程只能调用 pthread_join() 一次,并且不能调用了 pthread_detach() 后又调用 pthread_join(),否则可能会出现不可预料的错误。
回调函数的返回值
把上面的程序稍加改动:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
static void* print_tid(void* arg)
{
int* t = malloc(sizeof(int));
*t = *((int*)arg) + 5;
printf("thread id: %d, return addr: %p\n", *((int*)arg), t);
return t;
}
#define THREAD_NR 10
int main()
{
int status;
int i, tid[THREAD_NR], *ret[THREAD_NR];
pthread_t pt[THREAD_NR];
for (i = 0; i < THREAD_NR; ++i) {
tid[i] = i;
status = pthread_create(&pt[i], NULL, print_tid, &tid[i]);
if (status != 0)
fprintf(stderr, "create thread %d fail.\n", i);
}
for (i = 0; i < THREAD_NR; ++i) {
status = pthread_join(pt[i], (void**)(&ret[i]));
if (status != 0)
fprintf(stderr, "join thread %d fail.\n", i);
}
for (i = 0; i < THREAD_NR; ++i) {
printf("thread %d return %d, return addr: %p\n", i, *ret[i], ret[i]);
free(ret[i]);
}
return 0;
}
在回调函数中使用 malloc() 为返回值分配空间,调用 pthread_join() 时加上保存返回值的指针地址,最后的打印结果表明回调函数的返回值的确保存在 ret 数组中。
要结束线程并返回值除了使用 return 语句外,还可以使用函数 pthread_exit():
void pthread_exit(void *retval);
pthread_exit() 函数结束当前的线程并把传递给它的值保存在 pthread_join() 函数第二个参数中,并且会调用一些由 pthread_cleanup_push() 注册的函数(目前还不懂……)。在上面程序的 print_tid() 函数中,最后的 return 语句可以使用下面的语句代替:
pthread_exit(t);
pthread_exit() 不同于 exit(),前者只会结束当前线程,而后者则会结束整个进程。
参考资料
[1] Multithreaded Programming (POSIX pthreads Tutorial)
[2] Michael Kerrisk. The Linux Programming Interface.