
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.hvoid 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 i0; i9; 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 i0; i3; 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; }子线程函数里构造结构体拿到这块数据的内存地址0xaaapthread_exit( (void*)0xaaa )把子线程退出返回值设置成结构体数据的地址主线程定义一级指针void *ptr NULL;调用pthread_join(tid, ptr)ptr是ptr 变量自身的地址二级指针仅函数内部使用函数内部改写主线程的 ptrptr 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 的地址传给子线程函数子线程通过这个地址直接修改主线程栈上这块结构体的数据子线程执行到 i6 时调用pthread_exit(p)把同一个地址再次作为返回值传出主线程用pthread_join阻塞等待子线程结束回收线程资源主线程直接打印自己栈上变量p的值拿到子线程修改后的结果。线程分离pthread_detach()分离后主线程可以继续自己的操作线程取消pthread_cancel主线程调用线程取消函数只要在子线程中进行了系统调用当子线程执行到这个位置就挂掉了。关于系统调用有两种方式直接调用Linux系统函数调用标准C库函数为了实现某些功能在Linux平台下标准C库函数会调用相关的系统函数线程ID比较pthread_equal