pthread_t pthread_self(void);
int pthread_equal(pthread_t t1, pthread_t t2);
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
如果 pthread_create()调用成功,函数返回 0,否则返回一个非 0 的错误码, 下表列出 pthread_create()函数调用时必须检查的错误码。
错误码 | 出错说明 |
EAGAIN | 系统没有创建线程所需要的资源 |
EINVAL | attr参数无效 |
EPERM | 调用程序没有适当的权限来设定调度策略或attr指定的参数 |
参数说明:
- thread 用指向新创建的线程的 ID;
-
attr 用来表示一个封装了线程各种属性的属性对象,如果 attr 为 NULL ,新线程就使用默认的属性, 下面 第4部分 将讨论线程属性的细节;
-
start_routine 是线程开始执行的时候调用的函数的名字, start_routine 函数有一个有指向 void 的指针参数,并有 pthread_create 的第四个参数 arg 指定值,同时 start_routine 函数返回一个指向 void 的指针,这个返回值被 pthread_join 当做退出状态处理, 下面第 3部分 介绍线程的退出状态;
-
arg 为参数 start_routine 指定函数的参数。
进程的终止可以通过直接调用 exit()、执行 main()中的 return、或者通过进程的某个其它线程调用 exit()来实现。在以上任何一种情况下,所有的线程都会终止。如果主线程在创建了其它线程后没有任务需要处理,那么它应该阻塞等待所有线程都结束为止,或者应该调用pthread_exit(NULL)。
调用 exit()函数会使整个进程终止,而调用 pthread_exit()只会使得调用线程终止,同时在创建的线程的顶层执行 return 线程会隐式地调用 pthread_exit()。 pthread_exit()函数原型如下:
void pthread_exit(void *retval);
retval 是一个 void 类型的指针,可以将线程的返回值当作 pthread_exit()的参数传入,这个值同样被 pthread_join()当作退出状态处理。如果进程的最后一个线程调用了 pthread_exit(),进程会带着状态返回值 0 退出。
线程的创建与终止 : #include <pthread.h> #include <stdio.h> #include <stdlib.h> #define NUM_THREADS 5 void *PrintHello(void *threadid){ /* 线程函数 */ long tid; tid = (long)threadid; printf("Hello World! It's me, thread #%ld!\n", tid); /* 打印线程对应的参数 */ pthread_exit(NULL); } int main (int argc, char *argv[]) { pthread_t threads[NUM_THREADS]; int rc; long t; for(t=0; t<NUM_THREADS; t++){ /* 循环创建 5 个线程 */ printf("In main: creating thread %ld\n", t); rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t); /* 创建线程 */ if (rc){ printf("ERROR; return code from pthread_create() is %d\n", rc); exit(-1); } } printf("In main: exit!\n"); pthread_exit(NULL); /* 主线程退出 */ return 0; }程序清单 的程序运行结果如下图 所示,程序中主线程调用了 pthread_exit() 函数并不会将整个进程终止,而是最后一个线程调用 pthread_exit() 时程序才完成运行。
注意:由于操作系统调度的线程的随机性,多线程程序的执行结果可能与本文给出的结果不一致。
3 连接与分离
线程可以分为分离线程(DETACHED)和非分离线程(JOINABLE)两种:
- 分离线程是指线程退出时线程将释放它的资源的线程;
- 非分离线程退出后不会立即释放资源,需要另一个线程为它调用 pthread_join 函数或者进程退出时才会释放资源。
只有非分离线程才是可连接的,而分离线程退出时不会报告线程的退出状态。
3.1 线程分离
pthread_detach()函数可以将非分离线程设置为分离线程,函数原型如下:
int pthread_detach(pthread_t thread);
参数 thread 是要分离的线程的 ID。
pthread_detach(pthread_self());成功返回 0 ;失败返回一个非 0 的错误码, 下表 列出 pthread_detach 的实现必须检查的错误码。
错误码 | 出错描述 |
EINVAL | thread参数所表示的线程不是可分离的线程 |
ESRCH | 没有找到线程ID为thread的线程 |
3.2 线程连接
如果一个线程是非分离线程,那么其它线程可调用 pthread_join()函数对非分离线程进行连接。 pthread_join()函数原型如下:
int pthread_join(pthread_t thread, void **retval);pthread_join() 函数将调用线程挂起,直到第一个参数 thread 指定目标线程终止运行为止。
参数 retval 为指向线程的返回值的指针提供一个位置, 这个返回值是目标线程调用pthread_exit()或者 return 所提供的值。当目标线程无需返回时可使用 NULL 值,调用线程如果不需对目标线程的返回状态进行检查可直接将 retval 赋值为 NULL。
如果 pthread_join() 成功调用,它将返回 0 值,如果不成功, pthread_join() 返回一个非 0 的错误码, 下表 列出 pthread_join() 的实现必须检查的错误码。错误码 | 出错描述 |
EINVAL | thread参数所表示的线程不是可分离的线程 |
ESRCH | 没有找到线程ID为thread的线程 |
3.3 线程范例-2
下列程序清单给出了 pthread_join()的使用范例,主线程创建了 4 个线程来进行数学运算,每个线程将运算的结果使用 pthread_exit()函数返回给主线程,主线程使用 pthread_join()等待 4 个线程完成和获取线程的运行结果
pthread_join 函数示例 #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <math.h> #define NUM_THREADS 4 void *BusyWork(void *t) /* 线程函数 */ { int i; long tid; double result=0.0; tid = (long)t; printf("Thread %ld starting...\n",tid); for (i=0; i<1000000; i++) { result = result + sin(i) * tan(i); /* 进行数学运算 */ } printf("Thread %ld done. Result = %e\n",tid, result); pthread_exit((void*) t); /* 带计算结果退出 */ } int main (int argc, char *argv[]) { pthread_t thread[NUM_THREADS]; int rc; long t; void *status; for(t=0; t<NUM_THREADS; t++) { printf("Main: creating thread %ld\n", t); rc = pthread_create(&thread[t], NULL, BusyWork, (void *)t); /* 创建线程 */ if (rc) { printf("ERROR; return code from pthread_create() is %d\n", rc); exit(-1); } } for(t=0; t<NUM_THREADS; t++) { rc = pthread_join(thread[t], &status); /*等待线程终止,并获取返回值*/ if (rc) { printf("ERROR; return code from pthread_join() is %d\n", rc); exit(-1); } printf("Main: completed join with thread %ld having a status of %ld\n",t,(long)status); } printf("Main: program completed. Exiting.\n"); pthread_exit(NULL); }下图可以看出四个线程的计算结果相同,主线程在 4 个线程完成后退出。
前面介绍的线程创建 pthread_create()函数, pthread_create()函数的第二个参数为pthread_attr_t 类型, 用于设置线程的属性。
通常先创建一个属性对象,然后在属性对象上设置属性的值,再将属性对象传给pthread_create 函数的第二个参数用来创建含有该属性的线程。
一个属性对象可以多次传给 pthread_create()函数创建多个含有相同属性的线程。
4.1 属性对象
int pthread_attr_init(pthread_attr_t *attr);函数只有一个参数, 是一个指向 pthread_attr_t 的属性对象的指针。成功返回 0 , 否则返回一个非 0 的错误码。
销毁属性对象使用 pthread_attr_destroy()函数, 函数原型如下:
int pthread_attr_destroy(pthread_attr_t *attr);
函数只有一个参数, 是一个指向 pthread_attr_t 的属性对象的指针。成功返回 0, 否则返回一个非 0 的错误码。
- PTHREAD_CREATE_JOINABLE——非分离线程;
- PTHREAD_CREATE_DETACHED——分离线程。
( 1)获取线程状态
获取线程状态的函数是 pthread_attr_getdetachstate(),原型如下:
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);参数 attr 是一个指向已初始化的属性对象的指针, detachstate 是获取结果值的指针。成功返回 0 ,否则返回一个非 0 的错误码。
( 2)设置线程状态
设置线程状态的函数是 pthread_attr_setdetachstate(), 原型如下:
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);参数 attr 是一个指向已初始化的属性对象的指针, detachstate 是要设置的值。成功返回 0 ,否则返回一个非 0 的错误码。
4.3 线程栈
int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);参数 attr 都是一个指向已初始化的属性对象的指针, stacksize 是获取的栈大小的指针。成功返回 0 ,否则返回一个非 0 的错误码。
设置线程栈大小的函数是 pthread_attr_setstacksize(),原型如下:
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
参数 attr 都是一个指向已初始化的属性对象的指针, stacksize 是设置的栈大小。成功返回 0,否则返回一个非 0 的错误码。
线程属性示例 #include <pthread.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <ctype.h> #define handle_error_en(en, msg) \ /* 出错处理宏供返回错误码的函数使用 */ do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0) #define handle_error(msg) \ /* 出错处理宏 */ do { perror(msg); exit(EXIT_FAILURE); } while (0) struct thread_info { pthread_t thread_id; int thread_num; char *argv_string; }; static void *thread_start(void *arg){ /* 线程运行函数 */ struct thread_info *tinfo = arg; char *uargv, *p; printf("Thread %d: top of stack near %p; argv_string=%s\n", /* 通过 p 的地址来计算栈的起始地址*/ tinfo->thread_num, &p, tinfo->argv_string); uargv = strdup(tinfo->argv_string); if (uargv == NULL) handle_error("strdup"); for (p = uargv; *p != '\0'; p++) *p = toupper(*p); /* 小写字符转换大写字符 */ return uargv; /* 将转换结果返回 */ } int main(int argc, char *argv[]) { int s, tnum, opt, num_threads; struct thread_info *tinfo; pthread_attr_t attr; int stack_size; oid *res; stack_size = -1; while ((opt = getopt(argc, argv, "s:")) != -1) { /* 处理参数-s 所指定的栈大小 */ switch (opt) { case 's': stack_size = strtoul(optarg, NULL, 0); break; default: fprintf(stderr, "Usage: %s [-s stack-size] arg...\n",argv[0]); exit(EXIT_FAILURE); } } num_threads = argc - optind; s = pthread_attr_init(&attr); /* 初始化属性对象 */ if (s != 0) handle_error_en(s, "pthread_attr_init"); if (stack_size > 0) { s = pthread_attr_setstacksize(&attr, stack_size); /* 设置属性对象的栈大小 */ if (s != 0) handle_error_en(s, "pthread_attr_setstacksize"); } tinfo = calloc(num_threads, sizeof(struct thread_info)); if (tinfo == NULL) handle_error("calloc"); for (tnum = 0; tnum < num_threads; tnum++) { tinfo[tnum].thread_num = tnum + 1; tinfo[tnum].argv_string = argv[optind + tnum]; s = pthread_create(&tinfo[tnum].thread_id, &attr, /* 根据属性创建线程 */ &thread_start, &tinfo[tnum]); if (s != 0) handle_error_en(s, "pthread_create"); } s = pthread_attr_destroy(&attr); /* 销毁属性对象 */ if (s != 0) handle_error_en(s, "pthread_attr_destroy"); for (tnum = 0; tnum < num_threads; tnum++) { s = pthread_join(tinfo[tnum].thread_id, &res); /* 等待线程终止,并获取返回值 */ if (s != 0) handle_error_en(s, "pthread_join"); printf("Joined with thread %d; returned value was %s\n", tinfo[tnum].thread_num, (char *) res); free(res); } free(tinfo); exit(EXIT_SUCCESS); }
下图 是程序清单 的一个运行结果,运行此程序是使用 -s 参数指定每个创建线程的栈大小,每个线程运行起来后都先取栈变量的地址用过打印变量地址来大概估计栈起始的地址。然后每个线程将线程参数给出的字符串转换为大写并返回给主线程,主线程使用
pthread_join() 等待并获取线程的结果。