Linux多线程
2026/9/16 11:08:48 网站建设 项目流程

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; }
  1. 子线程函数里构造结构体,拿到这块数据的内存地址0xaaa
  2. pthread_exit( (void*)0xaaa ):把子线程退出返回值设置成结构体数据的地址
  3. 主线程定义一级指针void *ptr = NULL;,调用pthread_join(tid, &ptr)
    • &ptr:是ptr 变量自身的地址(二级指针,仅函数内部使用)
    • 函数内部改写主线程的 ptr:ptr = 0xaaa
  4. 执行完 join 之后:ptr 就是保存结构体数据地址的一级指针
  5. struct Persion *pp = (struct Persion *)ptr;
    • pp同样是结构体类型的一级指针
    • 强制转换不是为了 “匹配 ptr 本身存储的值(地址数字不变)”,而是给编译器补充内存类型信息
    • ptr 是无类型万能指针void*,编译器不知道这块内存布局;强转为struct Persion*之后,编译器才认识->name / ->age这种结构体成员访问语法。

ptrpp两个变量存完全相同的内存编号 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; }

实现思路

  1. 主线程在自己的栈上定义结构体变量struct Persion p
  2. 创建子线程,把主线程局部变量 p 的地址传给子线程函数
  3. 子线程通过这个地址,直接修改主线程栈上这块结构体的数据
  4. 子线程执行到 i==6 时调用pthread_exit(p),把同一个地址再次作为返回值传出;
  5. 主线程用pthread_join阻塞等待子线程结束,回收线程资源;
  6. 主线程直接打印自己栈上变量p的值,拿到子线程修改后的结果。

线程分离

pthread_detach()

分离后主线程可以继续自己的操作

线程取消

pthread_cancel

主线程调用线程取消函数,只要在子线程中进行了系统调用,当子线程执行到这个位置就挂掉了。

关于系统调用有两种方式:

直接调用Linux系统函数
调用标准C库函数,为了实现某些功能,在Linux平台下标准C库函数会调用相关的系统函数

线程ID比较

pthread_equal

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询