1.线程
一个进程里面可以分出多条执行小路,每一条小路就是一个线程。
把进程看作一栋房子:
- 房子(进程)拥有完整配套资源:水电、家具、空间(内存地址空间)
- 线程就是房子里不同干活的人
- 每个人私有:自己手上的笔记本(栈、寄存器)
- 所有人共享这套房子的全部东西(堆、全局变量、文件)
- 一个人出事(线程崩溃),整栋房子直接报废(进程退出)
// pthread_create.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> //子线程执行代码 void* working(void* arg) { printf("子线程ID:%ld\n", pthread_self()); for (int i = 0; i < 3; i++) { printf("child=%d\n", i); } } int main() { //创建一个子线程 pthread_t tid; pthread_create(&tid, NULL, working, NULL); //父线程执行代码 printf("父线程ID:%ld\n", pthread_self()); for (int i = 0; i < 3; i++) { printf("father=%d\n", i); } while (1) { sleep(2); } return 0; }线程退出
#include <pthread.h>
void pthread_exit(void *retval);
参数,指针,指向线程退出时携带的数据
线程回收
#include <pthread.h> // 这是一个阻塞函数, 子线程在运行这个函数就阻塞 // 子线程退出, 函数解除阻塞, 回收对应的子线程资源, 类似于回收进程使用的函数 wait() int pthread_join(pthread_t thread, void **retval);一次只能回收一个
二级指针。pthread_exit() 传递出的数据的地址的地址
回收子线程数据
在子线程退出的时候可以使用pthread_exit()的参数将数据传出,在回收这个子线程的时候可以通过phread_join()的第二个参数来接收子线程传递出的数据。
void*:万能指针,只保存地址,编译器不知道这个地址上放的是什么类型的数据(不知道是 int、字符串,还是结构体)
使用子线程栈
// pthread_join.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> // 定义结构 struct Persion { int id; char name[36]; int age; }; // 子线程的处理代码 void* working(void* arg) { printf("我是子线程, 线程ID: %ld\n", pthread_self()); for(int i=0; i<9; ++i) { printf("child == i: = %d\n", i); if(i == 6) { struct Persion p; p.age =12; strcpy(p.name, "tom"); p.id = 100; // 该函数的参数将这个地址传递给了主线程的pthread_join() pthread_exit(&p); } } return NULL; // 代码执行不到这个位置就退出了 } int main() { // 1. 创建一个子线程 pthread_t tid; pthread_create(&tid, NULL, working, NULL); printf("子线程创建成功, 线程ID: %ld\n", tid); // 2. 子线程不会执行下边的代码, 主线程执行 printf("我是主线程, 线程ID: %ld\n", pthread_self()); for(int i=0; i<3; ++i) { printf("i = %d\n", i); } // 阻塞等待子线程退出 void* ptr = NULL; // ptr是一个传出参数, 在函数内部让这个指针指向一块有效内存 // 这个内存地址就是pthread_exit() 参数指向的内存 pthread_join(tid, &ptr); // 打印信息 struct Persion* pp = (struct Persion*)ptr; printf("子线程返回数据: name: %s, age: %d, id: %d\n", pp->name, pp->age, pp->id); printf("子线程资源被成功回收...\n"); return 0; }- 子线程函数里构造结构体,拿到这块数据的内存地址
0xaaa; pthread_exit( (void*)0xaaa ):把子线程退出返回值设置成结构体数据的地址;- 主线程定义一级指针
void *ptr = NULL;,调用pthread_join(tid, &ptr);&ptr:是ptr 变量自身的地址(二级指针,仅函数内部使用)- 函数内部改写主线程的 ptr:
ptr = 0xaaa
- 执行完 join 之后:ptr 就是保存结构体数据地址的一级指针;
struct Persion *pp = (struct Persion *)ptr;pp同样是结构体类型的一级指针;- 强制转换不是为了 “匹配 ptr 本身存储的值(地址数字不变)”,而是给编译器补充内存类型信息;
- ptr 是无类型万能指针
void*,编译器不知道这块内存布局;强转为struct Persion*之后,编译器才认识->name / ->age这种结构体成员访问语法。
ptr和pp两个变量存完全相同的内存编号 0xaaa,唯一区别:指针类型不一样
但是代码有问题
p是子线程栈局部变量当pthread_exit执行完 →子线程销毁,子线程的整个栈全部释放回收地址0xaaa已经变成无效内存! 主线程后面通过pp去访问一块已经释放掉的内存 →野指针!结果不可控(随机垃圾值)
得到的数据会是乱码
位于同一虚拟地址空间中的线程,虽然不能共享栈区数据,但是可以共享全局数据区和堆区数据,因此在子线程退出的时候可以将传出数据存储到全局变量、静态变量或者堆内存中。在下面的例子中将数据存储到了全局变量中:
使用主线程栈
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> struct person { int id; int age; }; void* working(void* arg) { //在子线程里面直接修改p //新建一个结构体指针p用来接受arg struct person* p = (struct person*)arg; printf("我是子线程, 线程ID: %ld\n", pthread_self()); for (int i = 0; i < 9; ++i) { printf("child == i: = %d\n", i); if (i == 6) { //使用主线程栈内存 p->age = 12; p->id = 10; pthread_exit(p);//把同一个地址再次作为返回值传出; } } return NULL; } int main() { //创建一个子线程 pthread_t pid; struct person p; //主线程栈内存传递给子线程 pthread_create(&pid, NULL, working, &p);//p就是working函数里面的arg printf("子线程创建成功, 线程ID: %ld\n", pid); // 2. 子线程不会执行下边的代码, 主线程执行 printf("我是主线程, 线程ID: %ld\n", pthread_self()); for (int i = 0; i < 3; ++i) { printf("i = %d\n", i); } // 阻塞等待子线程退出 void* ptr = NULL; // ptr是一个传出参数, 在函数内部让这个指针指向一块有效内存 // 这个内存地址就是pthread_exit() 参数指向的内存 pthread_join(pid, &ptr); // 打印信息 printf("age: %d, id: %d\n",p.age, p.id); printf("子线程资源被成功回收...\n"); return 0; }实现思路
- 主线程在自己的栈上定义结构体变量
struct Persion p; - 创建子线程,把主线程局部变量 p 的地址传给子线程函数;
- 子线程通过这个地址,直接修改主线程栈上这块结构体的数据;
- 子线程执行到 i==6 时调用
pthread_exit(p),把同一个地址再次作为返回值传出; - 主线程用
pthread_join阻塞等待子线程结束,回收线程资源; - 主线程直接打印自己栈上变量
p的值,拿到子线程修改后的结果。
线程分离
pthread_detach()
分离后主线程可以继续自己的操作
线程取消
pthread_cancel
主线程调用线程取消函数,只要在子线程中进行了系统调用,当子线程执行到这个位置就挂掉了。
关于系统调用有两种方式:
直接调用Linux系统函数
调用标准C库函数,为了实现某些功能,在Linux平台下标准C库函数会调用相关的系统函数
线程ID比较
pthread_equal