1. 线程的基本概念
- 进程 = 一个资源 + 多个指令执行序列,这多个指令执行序列指的就是线程。
- 进程中的所有线程共享映射表,映射表不会变。pc指针指向的是下一条要执行的指令的位置。有多个指令执行序列,所以pc指针在不断改变
- 右图中,T1、T2是进程中的两个线程,他们共享进程的资源,也有自己的私有资源。
- 每个线程都有一个线程执行函数,私有资源就在函数对应的栈帧里面。函数又共享进程的所有资源,也就是代码段,数据段,堆里面的内容(没有栈区)。
- 一个进程的所有线程都共享进程的代码区、数据区、堆区(没有栈区)、环境变量和命令行参数、文件描述符、信号处理函数、当前目录、用户ID和组ID等
1.1 线程的好处
-
上图中,左右两个进程里都有各自的三个线程。
-
进程间要协调工作就必须进行通信,就需要借助内核。而线程间的通信就会高效许多,直接在用户空间就可以实现。提升了效率,节省了空间。
-
进程间的切换,需要切换进程的PCB(把映像,指令等进行切换),又得借助内核。而线程之间的切换直接在用户空间实现,所有线程共享映射表等资源。
-
但是多个线程访问一个进程中的资源时,会产生异步访问,造成一些麻烦。
2. 线程的创建
2.1 pthread_create(3)
- 对上图中 void *(* start_routine)(void *) 解释:
- 这个位置需要放一个返回值是void*, 形参是void*的函数。类似于 int a;
- Compile and link with -pthread : 编译和链接的时候要指定pthread库
2.2 pthread_selt(2)
2.3 代码示例
#include "t_stdio.h"
#include <pthread.h>
#include <unistd.h>
// 线程执行的函数
void *doit(void *arg){
printf("arg: %s\tpid:%d\ttid:%lu\n", (char *)arg, getpid(), pthread_self());
return NULL;
}
int main(void){
pthread_t tid;
// 创建一个新的线程
pthread_create(&tid, NULL, doit, "new");
// 到这里进程中有两个线程了,新建线程 和 主线程
// 并且这两个线程目前是异步的,sleep(1)让主线程先休眠
// 是为了确保主线程后执行完
sleep(1);
// 主线程执行的 ↓
doit("main");
return 0;
}
$ gcc thread.c -lpthread
$ ./a.out
arg: new pid:5221 tid:140456161720064
arg: main pid:5221 tid:140456169993984
3. 线程的终止
3.1 return 和 exit(3)
3.2 pthread_exit(3)
- 代码示例看下面的 pthread_join(3)
3.2 pthread_cancel(3)
- PTHREAD_CANCELED
- 代码示例看下面的 pthread_join(3)
4. 线程的汇合和分离
- 分离:
- 如果新建的线程设置了分离的状态,新建线程在终止时会自动把资源释放给系统,主线程不用再去等待回收。
- 汇合:
- 如果主线程先到达汇合点,但新建的线程还没有终止,主线程就会阻塞等待回收资源。如果新建线程先达到汇合点,那么主线程来的时候就会立即回收新建线程的资源
4.1 pthread_detach(3)
4.1.1 代码示例
#include "t_stdio.h"
#include <pthread.h>
#include <unistd.h>
void *doit(void *arg){
for(int i =0; i<5; i++) {
printf("thead: %s\tpid: %d\ttid: %lu\n", (char *)arg, getpid(), pthread_self());
usleep(5000000);
}
return NULL;
}
int main(void){
// 创建新线程
pthread_t tid;
pthread_create(&tid, NULL, doit, "new");
// 分离新线程
pthread_detach(tid);
for(int i =0; i<5; i++) {
printf("pid: %d\ttid: %lu\n", getpid(), pthread_self());
usleep(10000000);
}
return 0;
}
$ ./a.out
pid: 26507 tid: 139907840493312
thead: new pid: 26507 tid: 139907832305408
thead: new pid: 26507 tid: 139907832305408
pid: 26507 tid: 139907840493312
thead: new pid: 26507 tid: 139907832305408
thead: new pid: 26507 tid: 139907832305408
pid: 26507 tid: 139907840493312
thead: new pid: 26507 tid: 139907832305408
pid: 26507 tid: 139907840493312
pid: 26507 tid: 139907840493312
4.2 pthread_join(3)
- 注意第二个参数retval是二级指针, 状态码地址会存到*retval
4.2.1 代码示例
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *handle(void *arg){
printf("handle running ...\n");
sleep(3);
return (void *)1;
}
void *handle_1(void *arg){
printf("handle_1 running ...\n");
sleep(3);
// 终止当前线程, 会把(void *)3 传递给调用pthread_join的线程的退出状态码
// 且会把宏PTHREAD_CANCELED设置为退出状态码
pthread_exit((void *)3);
}
void *handle_2(void *arg) {
while(1){
printf("handle_2 running ...\n");
sleep(1);
}
return NULL;
}
int main(void){
// 1. retrun 退出
// retval存放执行void类型存储区的地址
void *retval;
pthread_t tid;
pthread_create(&tid, NULL, handle, NULL);
// pthread_join 汇合线程,这里是阻塞的(如果新建线程还没有终止,主线程阻塞等待)
// 线程tid,把退出状态码放到*(&retval),也就是放到retval中。
// 暂时理解为 被(void*)的1 放到 了 void* 类型的变量retval中。然后又被(int)强制转换以后输出
pthread_join(tid, &retval);
printf("handle exit code ...%d\n",(int )retval);
// 2. pthread_exit 退出
pthread_create(&tid, NULL, handle_1, NULL);
// 线程tid把退出状态码放到retval中
pthread_join(tid, &retval);
printf("handle1 exit code ...%d\n",(int )retval);
// 3. pthread_cancel 退出
pthread_create(&tid, NULL, handle_2, NULL);
sleep(3);
// 3s之后给线程tid发送取消请求
pthread_cancel(tid);
// 线程tid把退出状态码放到retval中
pthread_join(tid, &retval);
if(PTHREAD_CANCELED == retval){
printf("handle2 exit code ...%d\n",(int )retval);
}
return 0;
}
$ ./a.out
handle running ...
handle exit code ...1
handle_1 running ...
handle1 exit code ...3
handle_2 running ...
handle_2 running ...
handle_2 running ...
handle2 exit code ...-1