1 线程概念
1.1 进程到线程
多进程之间互相沟通比较麻烦,比如内存共享、描述符共享、互斥与同步。同一个进程中的多线程使用的资源是共享的,比如内存,文件描述符等等。
1.2 线程资源
线程共享资源:
可执行程序的代码,程序的全局内存,堆内存,栈,文件描述符。
线程独有资源:
线程 ID,线程自己的一套寄存器值,线程运行栈,调度优先级和策略,信号屏蔽字,errno 变量,线程私有数据。
1.3 进程线程区别
进程被创建出来,自动的就包含了一个主线程,进程中真正的执行体是线程。进程是一种空间上的概念,为所有这些执行体(线程)提供必要的资源(内存、文件描述符、代码等)。线程是时间上的概念,它是抽象的、假想的、动态的指令执行过程。把进程理解成工厂以及工厂里的各种设备和资源,而线程就是工厂里一个个干活的工人。
2 线程创建与终止
2.1 线程创建
typedef void *(*start_routine) (void *);
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, start_routine th_fn, void *arg);
-
thread: 传出参数,存储线程 id。函数pthread_self可以获取自己的线程 id 号。
-
attr: 传入参数,线程属性。NULL表示默认属性
-
th_fn: 线程入口函数。
-
arg: 线程入口函数th_fn的参数。
-
返回 0 表示成功。pthread_create 函数执行错误不会设置 errno 变量。
2.2 线程终止
包括主动终止和被动终止。
1、主动终止
-
线程过程函数执行
return
正常返回,返回值是线程的退出码 -
线种过程函数执行
pthread_exit
函数退出,其参数是线程退出码
2、被动终止
-
在其它线程中调用
thread_cancel
函数 -
任意线程调用 exit、_Exit 或者 _exit 导致整个进程终止
void pthread_exit(void *rval_ptr);
参数rval_ptr是线程退出码。通过函数 pthread_join
可以获取退出码。该函数类似于waitpid,回收线程资源。
int pthread_join(pthread_t tid, void **rval_ptr);
pthread_join
会阻塞,直到指定线程 tid 返回(return)、执行 pthread_exit
或者被其它线程取消(pthread_cancel
)。rval_ptr用来接收退出码。
int pthread_cancel(pthread_t tid);
相当于在线程 tid 中调用 pthread_exit((void*)PTHREAD_CANCEL)
函数。线程退出码都是 PTHREAD_CANCELED
实验:线程主动退出
// threadexit.c
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ERR(name,err) do{printf("%s:%s\n",#name,strerror(err));exit(-1);}while(0);
void* th_fn1(void* arg) {
puts("thread 1 returning");
return (void*)10;
}
void* th_fn2(void* arg) {
puts("thread 2 exiting");
pthread_exit((void*)20);
puts("thread 2 exited");
}
int main() {
pthread_t tid1, tid2;
int err;
void* ret;
err = pthread_create(&tid1, NULL, th_fn1, NULL);
if (err != 0) ERR(pthread_create1, err);
err = pthread_create(&tid2, NULL, th_fn2, NULL);
if (err != 0) ERR(pthread_create2, err);
sleep(2);
// 主线程阻塞直到线程 tid1 退出
err = pthread_join(tid1, &ret);
if (err != 0) ERR(pthread_join1, err);
printf("thread 1 exit code %d\n", (int)ret);
// 主线程阻塞直到线程 tid2 退出
err = pthread_join(tid2, &ret);
if (err != 0) ERR(pthread_join2, err);
printf("thread 2 exit code %d\n", (int)ret);
return 0;
}
编译运行
$ gcc threadexit.c -o threadexit -lpthread
$ ./threadexit
thread 1 returning
thread 2 exiting
thread 1 exit code 10
thread 2 exit code 20
实验:线程被动退出
// threadcancel.c
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ERR(name,err) do{printf("%s:%s\n",#name,strerror(err));exit(-1);}while(0);
void* th_fn1(void* arg) {
while (1) {
puts("thread 1 running");
sleep(1);
}
return (void*)10;
}
void* th_fn2(void* arg) {
while (1) {
puts("thread 2 running");
sleep(1);
}
pthread_exit((void*)20);
}
int main() {
pthread_t tid1, tid2;
int err;
void* ret;
err = pthread_create(&tid1, NULL, th_fn1, NULL);
if (err != 0) ERR(pthread_create1, err);
err = pthread_create(&tid2, NULL, th_fn2, NULL);
if (err != 0) ERR(pthread_create2, err);
sleep(5);
// 通知 tid1 和 tid2 退出。
pthread_cancel(tid1);
pthread_cancel(tid2);
err = pthread_join(tid1, &ret);
// 线程退出码都是 PTHREAD_CANCELED
printf("PTHREAD_CANCELED = %d\n", (int)PTHREAD_CANCELED);
if (err != 0) ERR(pthread_join1, err);
printf("thread 1 exit code %d\n", (int)ret);
err = pthread_join(tid2, &ret);
if (err != 0) ERR(pthread_join2, err);
printf("thread 2 exit code %d\n", (int)ret);
return 0;
}
编译运行
$ gcc threadcancel.c -o threadcancel -lpthread
$ ./threadcancel
thread 1 running
thread 2 running
thread 1 running
thread 2 running
thread 1 running
thread 2 running
thread 2 running
thread 1 running
thread 1 running
thread 2 running
PTHREAD_CANCELED = -1
thread 1 exit code -1
thread 2 exit code -1
3 线程清理函数
希望线程退出时自动执行某些函数
void pthread_cleanup_push(void (*rtn)(void*), void *arg);
void pthread_cleanup_pop(int execute);
push 和 pop 必须成对出现,arg为函数rtn的参数。
有三种情况线程清理函数会被调用:
-
线程还未执行 pthread_cleanup_pop 前,被 pthread_cancel 取消
-
线程还未执行 pthread_cleanup_pop 前,主动执行 pthread_exit 终止
-
线程执行 pthread_cleanup_pop,且 pthread_cleanup_pop 的参数不为 0
如果线程还未执行 pthread_cleanup_pop 前通过 return 返回,不会执行清理函数。
// clean.c
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ERR(name,err) do{printf("%s: %s",#name,strerror(err));exit(-1);}while(0);
int excute;
void cleanup(void* arg) {
printf("cleanup: %s\n", (char*)arg);
}
void* th_fn1(void* arg) {
puts("thread 1 starting");
pthread_cleanup_push(cleanup, "线程 1 清理者 1 号");
pthread_cleanup_push(cleanup, "线程 1 清理者 2 号");
if (arg) {
printf("线程 1 提前退出\n");
return (void*)1;
}
pthread_cleanup_pop(excute);
pthread_cleanup_pop(excute);
printf("线程 1 正常退出\n");
return (void*)10;
}
void* th_fn2(void* arg) {
puts("thread 2 starting");
pthread_cleanup_push(cleanup, "线程 2 清理者 1 号");
pthread_cleanup_push(cleanup, "线程 2 清理者 2 号");
if (arg) {
printf("线程 2 提前退出\n");
pthread_exit((void*)2);
}
pthread_cleanup_pop(excute);
pthread_cleanup_pop(excute);
printf("线程 2 正常退出\n");
pthread_exit((void*)20);
}
int main(int argc, char* argv[]) {
if (argc < 3) {
printf("Usage: %s <arg 0|1> <excute 0|1>\n", argv[0]);
return -1;
}
pthread_t tid1, tid2;
int err;
void* ret;
void* arg = NULL;
excute = 0;
arg = (void*)atoi(argv[1]);
excute = atoi(argv[2]);
err = pthread_create(&tid1, NULL, th_fn1, arg);
if (err != 0) ERR(pthread_create1, err);
err = pthread_create(&tid2, NULL, th_fn2, arg);
if (err != 0) ERR(pthread_create2, err);
err = pthread_join(tid1, &ret);
if (err != 0) ERR(pthread_join1, err);
printf("thread 1 exit code %d\n", (int)ret);
err = pthread_join(tid2, &ret);
if (err != 0) ERR(pthread_join2, err);
printf("thread 2 exit code %d\n", (int)ret);
return 0;
}
- 编译
$ gcc clean.c -o clean -lpthread
- 运行
可以看到:
- 当 clean 程序中的线程正常返回时,只有 pthread_cleanup_pop 的参数非 0 时,才会正常执行清理函数。
- 当 clean 程序中的线程在执行 pthread_cleanup_pop 前时,使用 pthread_exit 退出时,清理函数才会被执行,和 pthread_cleanup_pop 的参数没有关系。而使用 return 返回的线程 1 并不会执行清理函数。
- 清理函数的执行顺序,是按照注册时候相反的顺序执行的。