Linux 系统编程(6)——线程基础

发布时间:2026/8/18 23:57:31

Linux 系统编程(6)——线程基础
一、为什么需要线程进程的创建是为了提高并发但进程是重量级的——创建和切换的开销很大。线程的出现正是为了在保持并发能力的同时降低系统开销。1.1 进程与线程的本质区别对比项进程Process线程Thread定位资源分配的最小单位CPU 调度的基本单位侧重资源分配单位执行体、执行单位别称重量级进程轻量级进程LWP组成PCB [text | data | bss | 堆 | 栈]线程ID 程序计数器(PC) 一组相关寄存器 栈空间地址空间独立的虚拟地址空间共享所属进程的地址空间创建开销大需要复制页表、文件描述符表等小只需分配栈和少量管理结构关键关系线程共享进程的资源代码段、数据段、堆、文件描述符等线程存在于进程中一个进程至少有一个线程进程分为单线程进程和多线程进程1.2 Linux 内核中的线程Linux 早期并不原生支持线程而是通过clone()系统调用来实现——它创建调用进程的一个拷贝并与调用进程共享相同的地址空间。重要事实在 Linux 内核中实际上并不严格区分进程和线程。IBM 和 Red Hat 联合开发了NPTLNew/Native POSIX Thread Library——这是 Linux 上的线程函数库提供了标准的 POSIX 线程操作接口。我们后续使用的所有pthread_*系列函数都来自这个库。二、线程的四大生命周期线程操作与进程类似分为四个阶段创建 → 执行 → 退出 → 资源回收2.1 线程创建 ——pthread_createint pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);参数说明参数含义thread输出参数用于接收新线程的 IDattr线程属性传NULL表示使用默认属性可结合、非分离start_routine线程的执行函数——线程创建后从这个函数开始运行arg传给线程执行函数的参数返回值成功返回0失败返回错误码注意不是 -1。示例最简单的线程创建#include stdio.h #include pthread.h #include errno.h #include unistd.h // 线程执行函数 // 参数 arg 来自 pthread_create 的第四个参数 // 本例中为 NULL void *do_thread(void *arg) { // pthread_self() 获取当前线程自身的 ID printf(---do_thread ---- tid %ld\n, pthread_self()); return NULL; // 线程函数返回线程结束 } int main(int argc, const char *argv[]) { pthread_t tid; // 创建线程属性为 NULL默认执行函数为 do_thread参数为 NULL int ret pthread_create(tid, NULL, do_thread, NULL); if (ret ! 0) // 非零表示创建失败 { errno ret; // 将错误码设置到 errno perror(pthread_create fail); return -1; } printf(pthread_create success\n); printf(tid %ld\n, tid); // 打印子线程 ID printf(main tid %ld\n, pthread_self()); // 打印主线程 ID sleep(1); // 主线程等待 1 秒确保子线程有时间执行 return 0; }注意线程之间是对等关系不存在进程间的父子关系。2.2 线程执行 —— 传参与共享资源线程共享进程空间操作同一个资源时会相互影响。下面通过两个线程操作同一变量来展示示例两个线程操作共享变量#include stdio.h #include pthread.h #include errno.h #include unistd.h // 线程1每次将 var 加 1 void *do_thread1(void *arg) { int *var arg; // 接收主线程传来的变量地址 while (1) { (*var); // 对共享变量做 1 操作 printf(---do_thread1 ---- %d \n, *var); sleep(1); // 每秒操作一次 } return NULL; } // 线程2每次将 var 加 2 void *do_thread2(void *arg) { int *var arg; // 同样接收变量地址 while (1) { (*var) 2; // 对共享变量做 2 操作 printf(---do_thread2 ---- %d \n, *var); sleep(1); } return NULL; } int main(int argc, const char *argv[]) { int var 0; // 共享变量 pthread_t tid[2]; // 创建线程1将 var 作为参数传入 int ret pthread_create(tid[0], NULL, do_thread1, var); if (ret ! 0) { errno ret; perror(pthread_create fail); return -1; } // 创建线程2同样传入 var ret pthread_create(tid[1], NULL, do_thread2, var); if (ret ! 0) { errno ret; perror(pthread_create fail); return -1; } printf(pthread_create success\n); while (1) sleep(1); // 主线程无限等待保持进程存活 return 0; }运行结果示例---do_thread1 ---- 1 ---do_thread2 ---- 3 ---do_thread1 ---- 4 ---do_thread2 ---- 6 ---do_thread1 ---- 7 ...可以看到两个线程交替修改同一个变量var这正是资源共享、相互影响的体现。在没有同步机制的情况下这属于竞态条件Race Condition实际项目中需要用互斥锁等机制来保护。2.3 线程退出线程有四种退出方式方式说明1. 调用pthread_exit()主动退出可携带退出状态值2. 从线程执行函数中return等价于调用pthread_exit(return值)3. 被其他线程pthread_cancel()被动取消4. 进程中任意线程调用exit()或 main 函数 return导致整个进程终止所有线程都结束pthread_exit函数void pthread_exit(void *retval);功能结束调用线程。参数retval是线程带出的退出状态值一个指针。注意事项pthread_exit传递的是退出状态值对应空间的地址如果想获取该线程的退出状态值需要由另一个线程通过pthread_join来获取如果在 main 线程中调用pthread_exit()只是结束了主线程的执行并不会结束整个进程。进程会等待所有子线程执行结束后才销毁示例线程返回字符串#include stdio.h #include pthread.h #include errno.h #include unistd.h void *do_thread(void *arg) { static int ret 10; while (1) { printf(---do_thread ---- tid %ld\n, pthread_self()); } // 以下三种退出方式效果等价在实际代码中只会用一种 // return NULL; // pthread_exit(ret); return i am dead....; // 返回一个字符串指针作为退出状态值 } int main(int argc, const char *argv[]) { pthread_t tid; int ret pthread_create(tid, NULL, do_thread, NULL); if (ret ! 0) { errno ret; perror(pthread_create fail); return -1; } printf(pthread_create success\n); printf(tid %ld\n, tid); printf(main tid %ld\n, pthread_self()); #if 0 // 以下代码被条件编译禁用但展示了如何获取线程返回值 void *retval NULL; pthread_join(tid, retval); // 等待子线程结束获取退出值 // printf(retval %d\n, *(int *)retval); // 如果返回的是 int 指针 printf(retval %s\n, (char *)retval); // 返回的是字符串指针 #endif pthread_exit(NULL); // 主线程退出但进程不会立即结束 return 0; }2.4 线程资源回收 ——pthread_joinint pthread_join(pthread_t thread, void **retval);参数含义thread要等待的线程的 tidretval输出参数获取线程的退出状态值返回值成功返回0失败返回错误码。前提线程必须是可结合属性Joinable——这是创建线程时的默认属性。示例无人机多线程模型控制、视频、传输、存储这个示例模拟了一个无人机系统包含 4 个子线程分别负责控制、视频、传输和存储功能#include stdio.h #include pthread.h #include stdlib.h #include errno.h #include unistd.h // 定义线程函数指针类型方便批量管理 typedef void *(*threadFunc_t)(void *); // 控制线程运行 5 秒 void *do_ctrl(void *arg) { static int ret 1; // static 变量确保函数返回后地址仍有效 int n 5; while (n) { printf(-----%s----\n, __func__); // 打印函数名 do_ctrl sleep(1); --n; } return ret; // 返回退出状态值的地址 } // 视频线程运行 4 秒 void *do_video(void *arg) { static int ret 2; int n 4; while (n) { printf(-----%s----\n, __func__); sleep(1); --n; } return ret; } // 传输线程运行 3 秒 void *do_trans(void *arg) { static int ret 3; int n 3; while (n) { printf(-----%s----\n, __func__); sleep(1); --n; } return ret; } // 存储线程运行 2 秒 void *do_store(void *arg) { static int ret 4; int n 2; while (n) { printf(-----%s----\n, __func__); sleep(1); --n; } return ret; } int main(int argc, const char *argv[]) { pthread_t tid[4]; // 用函数指针数组统一管理 4 个线程函数 threadFunc_t tfunc[4] {do_ctrl, do_video, do_trans, do_store}; int i 0; // 批量创建 4 个线程 for (i 0; i 4; i) { int ret pthread_create(tid[i], NULL, tfunc[i], NULL); if (ret ! 0) { errno ret; perror(pthread_create fail); return -1; } } // 批量回收 4 个线程并获取各自的退出状态值 for (i 0; i 4; i) { int *retval NULL; // 阻塞等待第 i 个线程结束并将退出值地址存入 retval pthread_join(tid[i], (void **)retval); // 通过指针解引用获取实际的返回值 printf(ret %d \n, *retval); } return 0; }运行结果示例-----do_ctrl---- -----do_video---- -----do_trans---- -----do_store---- -----do_ctrl---- -----do_video---- -----do_trans---- -----do_store---- -----do_ctrl---- -----do_video---- -----do_ctrl---- -----do_ctrl---- ret 1 ret 2 ret 3 ret 4要点返回值指针必须指向static变量或全局变量/堆内存不能指向线程栈上的局部变量——否则线程退出后栈被销毁指针变成悬垂指针。三、线程属性与分离设置3.1 两种线程属性属性说明可结合Joinable默认线程结束后需要通过pthread_join回收资源分离Detached线程结束后系统自动回收资源无需也不能pthread_join3.2 设置分离属性的两种方式方式一通过属性对象创建时设置pthread_attr_t attr; pthread_attr_init(attr); // 初始化属性对象 pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED); // 设置分离属性 pthread_create(tid, attr, do_thread, NULL); // 用该属性创建线程 pthread_attr_destroy(attr); // 销毁属性对象方式二创建后动态设置使用pthread_detachpthread_create(tid, NULL, do_thread, NULL); // 默认可结合属性 pthread_detach(tid); // 创建后标记为分离状态示例使用pthread_detach设置分离属性#include stdio.h #include pthread.h #include errno.h #include unistd.h void *do_thread(void *arg) { printf(---do_thread ---- tid %ld\n, pthread_self()); return NULL; } int main(int argc, const char *argv[]) { // 以下是被注释的方式一代码 // pthread_attr_t attr; // pthread_attr_init(attr); // pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED); // int ret pthread_create(tid, attr, do_thread, NULL); pthread_t tid; // 使用默认属性可结合创建线程 int ret pthread_create(tid, NULL, do_thread, NULL); if (ret ! 0) { errno ret; perror(pthread_create fail); return -1; } pthread_detach(tid); // 创建后将线程标记为分离状态 // pthread_attr_destroy(attr); // 方式一需要销毁属性对象 printf(pthread_create success\n); printf(tid %ld\n, tid); printf(main tid %ld\n, pthread_self()); sleep(1); // 对已分离的线程调用 pthread_join 会失败 if ((ret pthread_join(tid, NULL)) ! 0) { errno ret; perror(pthread_join fail); // 会打印此错误Invalid argument return -1; } return 0; }运行输出---do_thread ---- tid xxxxx pthread_create success tid xxxxx main tid xxxxx pthread_join fail: Invalid argument关键点已经被pthread_detach的线程再调用pthread_join会失败返回EINVAL。3.3 子线程回收策略场景策略子线程能在有限时间内结束使用pthread_join正常等待回收子线程可能休眠或阻塞等待一定时间后强制回收子线程必须长时间运行设置为分离属性不再回收四、线程取消 ——pthread_cancelint pthread_cancel(pthread_t th);功能向目标线程发送取消请求。线程之间可以相互取消。示例子线程取消主线程#include stdio.h #include pthread.h #include errno.h #include unistd.h pthread_t mtid 0; // 全局变量保存主线程 ID void *do_thread(void *arg) { printf(---do_thread ---- tid %ld\n, pthread_self()); sleep(1); pthread_cancel(mtid); // 子线程取消主线程 return NULL; } void cleanup(void *arg) { printf(cleanup ----\n); // 线程清理函数 } int main(int argc, const char *argv[]) { pthread_t tid; mtid pthread_self(); // 记录主线程自身的 ID int ret pthread_create(tid, NULL, do_thread, NULL); if (ret ! 0) { errno ret; perror(pthread_create fail); return -1; } // 注册清理函数当主线程被取消时会触发 pthread_cleanup_push(cleanup, NULL); printf(pthread_create success\n); printf(tid %ld\n, tid); printf(main tid %ld\n, pthread_self()); sleep(5); // 主线程睡眠 5 秒但 1 秒后就会被子线程取消 // pthread_exit(NULL); while (1) sleep(1); pthread_cleanup_pop(0); // 正常流程中弹出不执行 return 0; }运行结果---do_thread ---- tid xxxxx pthread_create success tid xxxxx main tid xxxxx cleanup ---- ← 主线程被取消时触发了清理函数注意pthread_cleanup_pop(0)中参数为 0 表示弹出但不执行清理函数。但因为主线程在sleep(5)期间被子线程取消了清理函数会由取消机制自动触发执行。五、线程清理函数 ——pthread_cleanup_push / pop这是线程版的退出清理机制类似于进程中的atexit()。5.1 函数原型c// 注册清理函数 void pthread_cleanup_push(void (*routine)(void *), void *arg); // 弹出清理函数 void pthread_cleanup_pop(int execute);参数含义routine清理函数的入口地址arg传给清理函数的参数execute非零值弹出并执行清理函数0弹出但不执行5.2 触发方式清理函数在以下三种情况下会被自动触发触发条件说明pthread_cleanup_pop(非零值)主动弹出并执行pthread_exit()被调用线程退出时自动触发pthread_cancel()被调用线程被取消时自动触发5.3 实际代码中的使用模式pthread_cleanup_push(cleanup, NULL); // 注册清理函数 // ... 线程的工作代码 ... // 如果这里发生 pthread_cancel 或 pthread_exit // cleanup 会被自动调用 pthread_cleanup_pop(0); // 正常结束弹出但不执行示例pthread_cleanup_push/pop与pthread_cancel配合#include stdio.h #include pthread.h #include errno.h #include unistd.h // 线程执行函数 void *do_thread(void *arg) { printf(---do_thread ---- tid %ld\n, pthread_self()); return NULL; } // 线程清理函数——当线程被取消或调用 pthread_exit 时执行 void cleanup(void *arg) { printf(cleanup ----\n); } int main(int argc, const char *argv[]) { pthread_t tid; int ret pthread_create(tid, NULL, do_thread, NULL); if (ret ! 0) { errno ret; perror(pthread_create fail); return -1; } // 在主线程中注册清理函数 pthread_cleanup_push(cleanup, NULL); printf(pthread_create success\n); printf(tid %ld\n, tid); printf(main tid %ld\n, pthread_self()); sleep(1); // 等待子线程执行 // pthread_cleanup_pop(1); ← 如果参数为 1会直接执行 cleanup return 0; // 主线程正常返回 }展开原理pthread_cleanup_push和pthread_cleanup_pop在底层实际上是宏它们内部使用do { ... } while(0)结构配合setjmp/longjmp来实现清理函数的注册和调用。因此它们必须成对使用不能缺少任何一个。六、综合实战线程计算器需求从主线程获取用户输入的表达式如3.5*2.0组织成结构体传给子线程子线程计算后将结果存回结构体主线程输出结果。#include stdio.h #include pthread.h #include errno.h // 定义计算结构体 typedef struct { float a; // 第一个操作数 float b; // 第二个操作数 char c; // 运算符 (, -, *, /) float d; // 计算结果 } cal_t; // 加法线程 void *do_add(void *arg) { cal_t *p arg; // 接收结构体指针 p-d p-a p-b; // 计算结果直接存回结构体 return NULL; } // 减法线程 void *do_sub(void *arg) { cal_t *p arg; p-d p-a - p-b; return NULL; } // 乘法线程 void *do_mul(void *arg) { cal_t *p arg; p-d p-a * p-b; return NULL; } // 除法线程 void *do_div(void *arg) { cal_t *p arg; p-d p-a / p-b; // 注意未处理除零错误 return NULL; } int main(int argc, const char *argv[]) { cal_t s; printf(Input a pression:); // 格式化输入如 3.5*2.0 会被解析为 a3.5, c*, b2.0 scanf(%f%c%f, s.a, s.c, s.b); pthread_t tid; int ret 0; // 根据运算符创建对应的计算线程 switch (s.c) { case : ret pthread_create(tid, NULL, do_add, s); // 传入结构体地址 if (ret ! 0) { errno ret; perror(pthread_create fail); return -1; } pthread_join(tid, NULL); // 等待计算完成 printf(result %.2f\n, s.d); // 从结构体读取结果 break; case -: ret pthread_create(tid, NULL, do_sub, s); if (ret ! 0) { errno ret; perror(pthread_create fail); return -1; } pthread_join(tid, NULL); printf(result %.2f\n, s.d); break; case *: ret pthread_create(tid, NULL, do_mul, s); if (ret ! 0) { errno ret; perror(pthread_create fail); return -1; } pthread_join(tid, NULL); printf(result %.2f\n, s.d); break; case /: ret pthread_create(tid, NULL, do_div, s); if (ret ! 0) { errno ret; perror(pthread_create fail); return -1; } pthread_join(tid, NULL); printf(result %.2f\n, s.d); break; } pthread_exit(NULL); // 主线程退出 return 0; }#include stdio.h #include pthread.h #include errno.h // 定义计算结构体 typedef struct { float a; // 第一个操作数 float b; // 第二个操作数 char c; // 运算符 (, -, *, /) float d; // 计算结果 } cal_t; // 加法线程 void *do_add(void *arg) { cal_t *p arg; // 接收结构体指针 p-d p-a p-b; // 计算结果直接存回结构体 return NULL; } // 减法线程 void *do_sub(void *arg) { cal_t *p arg; p-d p-a - p-b; return NULL; } // 乘法线程 void *do_mul(void *arg) { cal_t *p arg; p-d p-a * p-b; return NULL; } // 除法线程 void *do_div(void *arg) { cal_t *p arg; p-d p-a / p-b; // 注意未处理除零错误 return NULL; } int main(int argc, const char *argv[]) { cal_t s; printf(Input a pression:); // 格式化输入如 3.5*2.0 会被解析为 a3.5, c*, b2.0 scanf(%f%c%f, s.a, s.c, s.b); pthread_t tid; int ret 0; // 根据运算符创建对应的计算线程 switch (s.c) { case : ret pthread_create(tid, NULL, do_add, s); // 传入结构体地址 if (ret ! 0) { errno ret; perror(pthread_create fail); return -1; } pthread_join(tid, NULL); // 等待计算完成 printf(result %.2f\n, s.d); // 从结构体读取结果 break; case -: ret pthread_create(tid, NULL, do_sub, s); if (ret ! 0) { errno ret; perror(pthread_create fail); return -1; } pthread_join(tid, NULL); printf(result %.2f\n, s.d); break; case *: ret pthread_create(tid, NULL, do_mul, s); if (ret ! 0) { errno ret; perror(pthread_create fail); return -1; } pthread_join(tid, NULL); printf(result %.2f\n, s.d); break; case /: ret pthread_create(tid, NULL, do_div, s); if (ret ! 0) { errno ret; perror(pthread_create fail); return -1; } pthread_join(tid, NULL); printf(result %.2f\n, s.d); break; } pthread_exit(NULL); // 主线程退出 return 0; }运行示例textInput a pression:3.5*2.0 result 7.00Input a pression:3.5*2.0 result 7.00设计亮点利用结构体指针实现主线程和子线程之间的数据传递——主线程负责输入和输出子线程负责计算。通过pthread_join确保计算完成后才读取结果。七、进程 vs 线程性能对比7.1 fork 性能测试创建 10000 个子进程#include stdio.h #include stdlib.h #include sys/types.h #include sys/wait.h #include unistd.h int main(int argc, const char *argv[]) { int i 10000; pid_t pid; for (i 10000; i 0; --i) { pid fork(); // 创建子进程开销大复制页表等 if (pid 0) // 父进程 { wait(NULL); // 等待子进程结束回收资源 continue; } else if (pid 0) // 子进程 { exit(100); // 立即退出 } } return 0; }7.2 线程性能测试创建 10000 个线程#include stdio.h #include pthread.h // 线程函数直接退出 void *thread(void *arg) { pthread_exit(NULL); } int main(int argc, const char *argv[]) { pthread_t tid; int i 10000; for (; i 0; --i) { // 创建线程开销小只需分配栈 int ret pthread_create(tid, NULL, thread, NULL); pthread_join(tid, NULL); // 等待并回收 } return 0; }结论同样创建 10001 个执行体线程版本的执行速度远快于进程版本。因为fork需要复制整个进程的地址空间、页表、文件描述符表等而pthread_create只需分配少量栈空间和管理结构。编译线程程序时需要链接 pthread 库gcc -o test test.c -lpthread八、查看线程的 Linux 命令# 查看所有线程LWP Light Weight Process ps -eLf # 更详细地查看线程信息 ps -eLo pid,ppid,lwp,stat,comm输出中的LWP列就是线程 ID同一进程的多个线程共享相同的PID。九、总结速查表9.1 线程 API 速查函数功能对应进程函数pthread_create创建线程forkpthread_exit退出线程exitpthread_join等待线程结束并回收资源waitpthread_cancel取消线程killpthread_detach设置线程为分离状态—pthread_self获取当前线程 IDgetpidpthread_cleanup_push注册清理函数atexitpthread_cleanup_pop弹出并可选执行清理函数—pthread_attr_init初始化属性对象—pthread_attr_destroy销毁属性对象—pthread_attr_setdetachstate设置分离属性—9.2 线程退出方式总结线程结束的 4 种方式 ├── 1. pthread_exit(retval) ← 主动退出可携带状态值 ├── 2. return retval ← 从执行函数返回 ├── 3. pthread_cancel(tid) ← 被其他线程取消 └── 4. exit() / main() return ← 整个进程终止所有线程都结束9.3 清理函数触发条件pthread_cleanup_push 注册的清理函数在以下情况触发 ├── 1. pthread_cleanup_pop(非零值) ← 主动触发 ├── 2. 线程调用 pthread_exit() ← 退出时触发 └── 3. 线程被 pthread_cancel() ← 被取消时触发十、注意事项与常见陷阱1.线程共享资源需同步保护多线程操作同一变量时需要使用互斥锁pthread_mutex等机制否则会出现竞态条件。2.pthread_exit不等于进程退出在 main 函数中调用pthread_exit()只是结束主线程进程会等所有子线程结束后才退出。3.返回值指针的生命周期线程函数返回的指针不能指向栈上的局部变量应该指向static变量、全局变量或堆内存。4.pthread_cleanup_push/pop必须配对它们是宏实现必须在同一代码块中成对出现否则编译报错。5.分离后的线程不能再pthread_join对已分离的线程调用pthread_join会返回EINVAL。6.编译时别忘了-lpthread使用 POSIX 线程函数库需要链接 pthread 库。

相关新闻

RAG系统检索效果调优:从向量检索到混合检索与重排序的工程实践

RAG系统检索效果调优:从向量检索到混合检索与重排序的工程实践

2026/8/18 23:57:31

上周,一个做企业知识库的朋友深夜发来消息,说他们基于RAG的问答系统上线后,用户反馈“时灵时不灵”。同一个问题,第一次问能答对,第二次问就答非所问;简单问题答得挺好,稍微复杂点、需要综合多个…

车联网如何重塑ADAS:从单车智能到协同感知的实战解析

车联网如何重塑ADAS:从单车智能到协同感知的实战解析

2026/8/18 23:57:31

1. 从ADAS到车联网:一场关于“车”的认知革命如果你和我一样,在汽车电子或者智能驾驶这个圈子里泡了几年,就会发现一个很有意思的现象:早些年大家聊ADAS(高级驾驶辅助系统),焦点都在车上——这颗…

欧尚长行MPV深度解析:7座家用车性价比之选

欧尚长行MPV深度解析:7座家用车性价比之选

2026/8/18 23:47:31

1. 新车上市:欧尚长行价格与定位解析 今天早上刷新闻,看到一条消息,长安欧尚旗下的全新MPV车型——欧尚长行,正式公布了售价,价格区间定在了6.89万元到8.29万元。这个价格一出来,我身边好几个正在看家用车的…

单片机毕设项目:基于 STM32 或 51 单片机的按键参数设置土壤灌溉控制系统设计 基于 STM32 或 51 单片机的温室土壤温湿度监测控水系统设计(020603)

单片机毕设项目:基于 STM32 或 51 单片机的按键参数设置土壤灌溉控制系统设计 基于 STM32 或 51 单片机的温室土壤温湿度监测控水系统设计(020603)

2026/8/19 0:47:33

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

单片机毕设项目:基于 STM32/51 单片机的声光语音复合酒精报警设备开发 基于 STM32/51 单片机的掉电保存阈值酒精检测控制系统设计(020503)

单片机毕设项目:基于 STM32/51 单片机的声光语音复合酒精报警设备开发 基于 STM32/51 单片机的掉电保存阈值酒精检测控制系统设计(020503)

2026/8/19 0:47:33

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

单片机计算机毕设之基于单片机的气压数据采集与移动端蓝牙控制系统设计 基于 STM32/51 单片机的可调阈值气压声光报警装置设计(022403)

单片机计算机毕设之基于单片机的气压数据采集与移动端蓝牙控制系统设计 基于 STM32/51 单片机的可调阈值气压声光报警装置设计(022403)

2026/8/19 0:47:33

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

195.ABAP REUSE_ALV_GRID_DISPLAY 实战教程

195.ABAP REUSE_ALV_GRID_DISPLAY 实战教程

2026/8/19 0:47:33

摘要 SAP(System Applications and Products)作为全球企业级应用软件的标杆,承载了世界500强中超过80%企业的核心业务流程。本文不讨论SAP的辉煌历史,而是从一个具备编程基础的技术工程师视角,系统拆解SAP的ABAP开发核心链路。文章从SAP系统架构入手,解析ABAP程序的工作…

194.ABAP 标准表 / 排序表 / 哈希表 实战对比

194.ABAP 标准表 / 排序表 / 哈希表 实战对比

2026/8/19 0:47:33

摘要 SAP系统作为企业资源计划(ERP)领域的工业标准,其技术栈深度与业务复杂度远超常规应用开发。本文面向具备编程基础、希望系统掌握SAP开发的工程师,以ABAP语言为主线,从数据字典对象、Open SQL、内表操作、模块化程序四个核心维度切入,结合完整可运行的ABAP程序示例,…

把歌词搬进Windows 11任务栏:Taskbar-Lyrics插件使用指南,一次搞定安装配置与排障

把歌词搬进Windows 11任务栏:Taskbar-Lyrics插件使用指南,一次搞定安装配置与排障

2026/8/19 0:37:33

把歌词搬进Windows 11任务栏:Taskbar-Lyrics插件使用指南,一次搞定安装配置与排障 【免费下载链接】Taskbar-Lyrics BetterNCM插件,在任务栏上嵌入歌词,目前仅建议Windows 11 项目地址: https://gitcode.com/gh_mirrors/ta/Task…

【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

2026/8/17 1:28:42

✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。🍎 往期回顾关注个人主页:Matlab科研工作室👇 关注我领取海量matlab电子书和…

【双层规划,节点出清价,绿证交易,CVaR方法】两级电力市场环境下计及风险的省间交易商最优购电模型附Matlab代码

【双层规划,节点出清价,绿证交易,CVaR方法】两级电力市场环境下计及风险的省间交易商最优购电模型附Matlab代码

2026/8/18 1:03:22

✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。🍎 往期回顾关注个人主页:Matlab科研工作室👇 关注我领取海量matlab电子书和…

隐式mpc+自适应mpc+时变mpc,线性时变模型预测控制附Simulink仿真

隐式mpc+自适应mpc+时变mpc,线性时变模型预测控制附Simulink仿真

2026/8/17 8:40:51

✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。🍎 往期回顾关注个人主页:Matlab科研工作室👇 关注我领取海量matlab电子书和…

SQL 调优 [ 2 ]

SQL 调优 [ 2 ]

2026/8/19 0:07:32

type列详解EXPLAIN输出的type列描述了表是如何连接的,性能从最好到最差的排序如下:systemconsteq_refreffulltextref_or_nullindex_mergeunique_subqueryindex_subqueryrangeindexALL接下来我们对 type列 做详细讲解。我们都知道,想要评估一条…

正式评优怎么选投票工具?人人微投票审计级防刷能力实测

正式评优怎么选投票工具?人人微投票审计级防刷能力实测

2026/8/19 0:07:32

在线上投票工具遍地开花的今天,选择一个合适的平台,本质上是在做一道关于场景与需求的匹配题。人人微投票是一个很典型的案例——它的产品逻辑、技术架构和商业模式,都围绕着“正式评选”这个细分场景深度扎根,也因此形成了自己鲜…

15 天 3 连发:DeepSeek 的「机枪」节奏,到底在下什么棋?

15 天 3 连发:DeepSeek 的「机枪」节奏,到底在下什么棋?

2026/8/19 0:07:32

15 天 3 连发:DeepSeek 的「机枪」节奏,到底在下什么棋?回看 2026 年 8 月这半个月,DeepSeek 的动作密度堪称疯狂:月初端出便宜快速的 V4-Flash,8 月 13 日同一天甩出 V4-Pro 正式版 开源 Harness 框架&am…

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

2026/8/17 12:00:53

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…

导师推荐!2026最新AI论文工具测评与实用推荐

导师推荐!2026最新AI论文工具测评与实用推荐

2026/8/15 10:10:27

2026年真正好用的AI论文工具,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

告别游戏崩溃:XCOM 2模组管理器的智能革命

告别游戏崩溃:XCOM 2模组管理器的智能革命

2026/8/18 12:20:24

告别游戏崩溃:XCOM 2模组管理器的智能革命 【免费下载链接】xcom2-launcher The Alternative Mod Launcher (AML) is a replacement for the default game launchers from XCOM 2 and XCOM Chimera Squad. 项目地址: https://gitcode.com/gh_mirrors/xc/xcom2-lau…