线程是进程最小的执行单位。对一个进程而言可以有多个线程,每个线程完成自己的任务,最终协同完成程序的功能。这样的程序设计方法显然更加合理、更有利于提高整个程序的吞吐量。线程与进程最大的区别就是线程必须包含在一个进程之内,而不能单独存在。因此它们可以共享内存地址空间。这意味着多个线程自动地可以访问相同的存储地址空间和文件描述符。由于多个线程共享地址空间,处理好多个线程之间的同步也是要重点学习的内容。
与进程标识符 pid_t
类似,线程也需要一个类型来进行标识,这便是 pthread_t
数据结构。需要注意的是,它并非一个整数类型,而是一个结构体,不同的操作系统也有差异。所以一般通过接口函数对其操作:
#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);
返回值:若相等,返回非0数值;否则,返回0
pthread_equal
函数比较两个线程ID是否相同。
#include <pthread.h>
pthread_t pthread_self(void);
返回值:调用线程的线程ID
pthread_self
函数获得自身的线程ID。
每个进程都有一个主线程,通过 pthread_create
函数可以为进程新增线程。
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_arrt_t *restrict attr, void*(*start_rtn)(void*), void *restrict arg);
返回值:若成功,返回0;否则,返回错误编号
参数说明:
tpid
参数用来保存成功返回的线程ID; attr
参数用来设置线程的各种属性,通常可以传入NULL,创建一个默认属性的线程; start_rtn
参数是线程函数的地址。可以通过 arg
参数为其传入一个 (void*)
类型的 指针。该函数返回一个 void*
类型。
下面这个例子利用了前面的这些函数来简单练习一下:
void* func(void *); int main() { pthread_t tid; int err; if ((err = pthread_create(&tid, NULL, func, NULL)) != 0) { printf("pthread_create error\n"); return -1; } /* main thread sleep to let create thread execute */ sleep(2); return 0; } void *func(void *arg) { pthread_t tid; tid = pthread_self(); printf("this is thread tid %ld tid (0x%1x)\n", (unsigned long)tid, (unsigned long)tid); return (void*)0; }
如果进程中的任意线程调用了 exit
_Exit
_exit
那么整个进程就会退出。与此类似,如果信号的动作是终止进程,那么任一线程接收到该信号也会终止进程。线程可以使用下面的三种退出方式而不影响整个进程:
pthread_exit
函数pthread_exit
函数可以使线程终止,声明如下:
#include <pthread.h>
void pthread_exit(void *rval_ptr);
参数 rval_ptr
保存退出状态。对于其他线程而言,可以通过调用 pthread_join
来获得指定线程的退出状态。
#include <pthread.h>
int pthread_join(pthread_t tid, void **rval_ptr);
返回值:若成功则返回0,否则返回错误编号
调用 pthread_join
函数的进程将会一直阻塞,直到指定的线程调用 pthread_exit
从线程函数返回、或者被取消。如果线程只是从它的启动例程返回,rval_ptr
将包含返回码。如果线程被取消,由 rval_ptr
指定的内存单元就被设置为 PTHREAD_CANCELED
写个例子来练一下:
void *fun1(void *arg) { printf("thread1 is running\n"); pthread_exit((void*)1); } void *fun2(void *arg) { printf("thread2 is running\n"); pthread_exit((void*)2); } int main(void) { int err; pthread_t tid1, tid2; void *tret; if ((err = pthread_create(&tid1, NULL, fun1, NULL)) < 0) return -1; if ((err = pthread_create(&tid2, NULL, fun2, NULL)) < 0) return -1; if ((err = pthread_join(tid1, &tret)) != 0) return -1; else { printf("thread1 exit code is %d\n", (int)(tret)); } if ((err = pthread_join(tid2, &tret)) != 0) return -1; else { printf("thread2 exit code is %d\n", (int)tret); } return(0); }
线程可以通过调用 pthread_cancel
函数来请求取消同一进程中的其他线程。
#include <pthread.h>
int pthread_cancel(pthread_t tid);
返回值:若成功返回0,否则返回错误编号
pthread_cancel
并不等待线程终止,它仅仅提出请求。
线程可以安排它退出时需要调用的函数,这与进程可以调用 atexit
函数安排进程退出时 需要调用的函数是类似的。这样的函数称为 线程清理处理程序
#includ <pthread.h>
void pthread_cleanup_push(void (*trn)(void *), void *arg);
void pthread_cleanup_pop(int execute);
函数 pthread_cleanup_pop
用于注册线程清理处理程序,它们的执行顺序与注册顺序相反。 当遇到下面的三种情况线程会调用清理函数:
如果 execute
参数设置为0, 清理函数将不被调用。无论是哪种情况,pthread_cleanup_pop
都将删除上次 pthread_cleanup_push
调用建立的清理处理程序。下面是个小例子:
void cleanup(void *arg) { printf("cleanup: %s\n", (char *)arg); } void *fun1(void *arg) { printf("thread 1 start\n"); pthread_cleanup_push(cleanup, "thread 1 first handler"); pthread_cleanup_push(cleanup, "thread 1 second handler"); printf("thread 1 push complete\n"); if (arg) return ((void*)1); pthread_cleanup_pop(0); pthread_cleanup_pop(0); return ((void*)1); } void *fun2(void *arg) { printf("thread 2 start\n"); pthread_cleanup_push(cleanup, "thread 2 first handler"); pthread_cleanup_push(cleanup, "thread 2 second handler"); printf("thread 2 push complete\n"); if (arg) pthread_exit((void*)2); pthread_cleanup_pop(0); pthread_cleanup_pop(0); return ((void*)2); } int main(void) { int err; pthread_t tid1, tid2; void *tret; if ((err = pthread_create(&tid1, NULL, fun1, (void*)1)) < 0) return -1; if ((err = pthread_create(&tid2, NULL, fun2, (void*)1)) < 0) return -1; if ((err = pthread_join(tid1, &tret)) != 0) return -1; if ((err = pthread_join(tid2, &tret)) != 0) return -1; return 0; }