当前位置: 首页 > news >正文

【Linux】进程等待

文章目录

  • 进程等待
    • 进程等待必要性
    • 实验(见见猪跑)
    • 进程等待的方法
      • wait方法
      • waitpid**方法**
      • 宏的使用方法
      • 获取子进程status
    • 阻塞VS非阻塞
      • 概念对比
      • 非阻塞有什么好处
  • 具体代码实现
    • 进程的阻塞等待方式:
    • 进程的非阻塞等待方式:
    • 让父进程做其他任务

进程等待

进程等待必要性

  • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
    因此,进程等待是为了:
  1. 避免内存泄漏
  2. 获取子进程执行的结果
    1. 代码跑完,结果对->退出码
    2. 代码跑完,结果不对->退出码
    3. 代码运行异常->信号

等待就是通过系统调用,获取子进程退出码或者退出信号的方式,顺便释放内存

实验(见见猪跑)

#include<sys/types.h>  
#include<sys/wait.h>  
int main()
{pid_t id = fork();if(id == 0){int cnt = 10;while(cnt){printf("我是子进程:%d,父进程:%d,cnt:%d\n",getpid(),getppid(),cnt--);}exit(10);//此时都是S状态}//此时子进程变成Z状态sleep(15);pid_t ret = wait(NULL);//回收子进程if(id > 0){printf("wait success:%d\n",ret);}sleep(5);//父进程退出
}

waitpid用法;

#include<sys/types.h>  
#include<sys/wait.h>  
int main()
{pid_t id = fork();if(id == 0)//子进程返回0,父进程返回子进程id{int cnt = 10;while(cnt){printf("我是子进程:%d,父进程:%d,cnt:%d\n",getpid(),getppid(),cnt--);}exit(10);//退出的三种结果://1.代码跑完,结果是对的//2.代码跑完,结果是错的//3.代码没有跑完,出异常了//此时都是S状态}//此时子进程变成Z状态sleep(15);int status = 0;pid_t ret = waitpid(id,&status,0);//回收子进程//错误观念:此时status的值变成了10//因为status不是被整体使用的,有自己的位图结构if(id > 0){printf("wait success:%d,sig number:%d,child exit code:%d\n",ret,(status & 0x7F,(status>>8)&0xFF);}//sig number:0 child exit code:10sleep(5);//父进程退出
}

监控脚本:

ps ajx | head -1 && ps axj | grep mytest | grep -v grep
第一句话是把标题拿出来
第二句话是把matest的进程信息拿出来
第三句话是把grep本身的进程信息去掉
之后写一个循环语句
while :; do ps ajx | head -1 && ps axj | grep mytest | grep -v grep; sleep 1; done

![[Pasted image 20221212134644.png]]
(ctrl + z 退出)

进程等待的方法

wait方法

:!man 2 wait //可以查看到相关wait的用法信息
需要包含两个头文件:
#include<sys/types.h>  
#include<sys/wait.h>  
pid_t wait(int* status);  
返回值:  
成功返回被等待进程pid,失败返回-1。  
参数:  
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);  
返回值:  
当正常返回的时候waitpid返回收集到的子进程的进程ID;  
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;  
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;  
参数:  pid:  Pid=-1,等待任一个子进程。与wait等效。  Pid>0.等待其进程ID与pid相等的子进程。  status:  WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)  WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)  options:  WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

这里有一个问题:
status按理来说是会返回信号+退出码的,但是一个整数怎么能返回两个整数呢?
所以我们不应该吧status当作一个完整的整数,而是应该看作位图结构(后面会详解)

宏的使用方法

int ret = waitpid(id,&status,0)
if(ret > 0)
{//是否正常退出if(WIFEXITED(status)){//判断子进程运行结果是否OKprintf("exit code :%d\n",WEXITSTATUS(status));}else//出异常了(比如kill -9){//TODOprintf("child exit not normal\n");}
}
  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。
    ![[Pasted image 20221112090227.png]]

获取子进程status

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态信息。
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(本身是32位,但是这里我们只研究status低16比特位):

1.代码跑完,结果是对的
2.代码跑完,结果是错的
3.代码没有跑完,出异常了

终止信号(即退出码)用来表示是否正常结束(为0则表示正常退出)
用退出状态表示结果是否正确(通过kill -l进行对应数字的查看(比如发生段错误,除0操作等)),此时退出码是否为0或者是其他的,都无意义,我们不讨论它.
第七位是信号编号
![[Pasted image 20221112090250.png]]

获取退出状态:
(status>>8)&0xFF )
获取终止信号:
``(status & 0x7F))

#include<sys/types.h>
#include<stdio.h>
#include<sys/wait.h>  
int main()
{pid_t id = fork();if (id == 0){int cnt = 10;while (cnt){printf("我是子进程:%d,父进程:%d,cnt:%d\n", getpid(), getppid(), cnt--);sleep(1);}}int status = 0;//1.让OS释放子进程的僵尸状态//2.获取子进程的退出结果//3.在等待期间,子进程没有退出的时候,父进程只能阻塞等待.pid_t ret = waitpid(id, &status, 0);if (id > 0){printf("wait success: %d,sig number : %d ,child exit code: %d\n",ret,(status & 0x7F),(status>>8)&0xFF );}}
测试代码:  
#include <sys/wait.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <errno.h>  
int main( void )
{  pid_t pid;  if ( (pid=fork()) == -1 )  perror("fork"),exit(1);  if ( pid == 0 ){  sleep(20);  exit(10);  } else {  int st;  int ret = wait(&st);  if ( ret > 0 && ( st & 0X7F ) == 0 ){ // 正常退出  printf("child exit code:%d\n", (st>>8)&0XFF);  } else if( ret > 0 ) { // 异常退出  printf("sig code : %d\n", st&0X7F );  }  }  
}  
测试结果:  [root@localhost linux]# ./a.out #等20秒退出  child exit code:10  [root@localhost linux]# ./a.out #在其他终端kill掉  sig code : 9

等待的本质:
监测子进程退出的信息
并且将子进程退出信息通过status拿回来
这个信息(exit code ,signal)在子进程的PCB中

![[进程控制 2022-12-12 14.39.31.excalidraw]]

再谈进程退出:
1.进程退出会变成僵尸进程->会把自己的退出结果写到自己的task_struct
2.wait/waitpid是一个系统调用->OS->OS有资格也有能力去读取子进程的task_struct

阻塞VS非阻塞

概念对比

例子:

1.不挂电话,监测李四的状态->阻塞
2.张三->李四,本质是状态检测,如果没有就绪,直接返回->每一次都是一次非阻塞等待->多次非阻塞等待我们称为:轮询

打电话->系统调用wait/waitpid
张三->父进程
李四->子进程
我们上面写的代码都是阻塞等待
轮询等待:

#include<sys/types.h>
#include<stdio.h>
#include<sys/wait.h>  
int main()
{pid_t id = fork();if (id == 0){int cnt = 10;while (cnt){printf("我是子进程:%d,父进程:%d,cnt:%d\n", getpid(), getppid(), cnt--);sleep(1);}}int status = 0;while(1)//这是一个死循环,作为轮询等待的一个条件!{pid_t ret = waitpid(id, &status, WNOHANG);//wnohang:非阻塞:子进程没有退出时,父进程监测之后立即返回//如果没有设置WNOHANG,则会卡在这一步,除非子进程退出,或者你的id值设置得有问题.否则不会执行下面的代码if(ret == 0)//设置了WNOHANG才会可能出现返回值为0的情况{//waitpid调用成功&&子进程没有退出,我的waitpid没有等待失败//仅仅是监测到了子进程没有退出printf("wait done,but child is running\n",);}else if(ret > 0){//1.waitpid调用成功(等待成功)&&子进程退出了		printf("wait success: %d,sig number : %d ,child exit code: %d\n",ret,(status & 0x7F),(status>>8)&0xFF );break;}else{//waitpid调用失败,返回值为-1//eg.传入的id参数传错了printf("waitpid call failed\n");break;}sleep(1);}
}

非阻塞有什么好处

不会占用父进程的所有精力,可以在轮询期间干干其他的
[[指针进阶]]
[[typedef函数进阶]]

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <stdlib.h>#define TASK_NUM 10typedef void (*func_t)();
func_t other_task[TASK_NUM] = {NULL};void sync_disk()
{printf("这是一个刷新磁盘的任务\n");
}
void sync_log()
{printf("这是一个同步日志的任务\n");
}
void net_send()
{printf("这是一个网络发送的任务\n");
}void LoadTask(func_t func)
{for (int i = 0; i < TASK_NUM; i++){if (other_task[i] == NULL){other_task[i] = func;break;}}
}void InitTask()
{for (int i = 0; i < TASK_NUM; i++)other_task[i] = NULL;LoadTask(sync_disk);LoadTask(sync_log);LoadTask(net_send);
}void RunTask()
{for (int i = 0; i < TASK_NUM; i++){if (other_task[i] == NULL)continue;other_task[i]();}
}int main()
{pid_t id = fork();assert(id >= 0);if (id == 0){int cnt = 5;while (cnt){printf("我是子进程,我的pid是:%d,我的ppid是%d,我还有%dS存活\n", getpid(), getppid(), cnt--);sleep(1);}printf("我已经退出\n");exit(11);}InitTask();while (1){sleep(1);int status;pid_t ret_id = waitpid(id, &status, WNOHANG);if (ret_id > 0){printf("我是父进程,我已经回收子进程,ret_id是%d,退出码为%d,退出信号为%d\n", ret_id, (status >> 8) & 0xFF, status & 0x7F);exit(0);}else if (ret_id == 0){RunTask();continue;}else{printf("调用出错\n");}}
}

具体代码实现

进程的阻塞等待方式:

#define _CRT_SECURE_NO_WARNINGS 1
int main()
{pid_t pid;pid = fork();if (pid < 0) {printf("%s fork error\n", __FUNCTION__);return 1;}else if (pid == 0) { //childprintf("child is run, pid is : %d\n", getpid());sleep(5);exit(257);}else {int status = 0;pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5Sprintf("this is test for wait\n");if (WIFEXITED(status) && ret == pid) {printf("wait child 5s success, child return code is :%d.\n", WEXITSTATUS(status));}else {printf("wait child failed, return.\n");return 1;}}return 0;
}
运行结果:
[root@localhost linux] # . / a.out
child is run, pid is : 45110
this is test for wait
wait child 5s success, child return code is : 1.

进程的非阻塞等待方式:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{pid_t pid;pid = fork();if (pid < 0) {printf("%s fork error\n", __FUNCTION__);return 1;}else if (pid == 0) { //childprintf("child is run, pid is : %d\n", getpid());sleep(5);exit(1);}else {int status = 0;pid_t ret = 0;do{ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待->子进程没有退出,父进程检测时候,立即返回if (ret == 0) {//子进程没有退出,我的waitpid没有等待失败//仅仅是监测到了子进程没有退出printf("child is running\n");}sleep(1);} while (ret == 0);if (WIFEXITED(status) && ret == pid) {printf("wait child 5s success, child return code is :%d.\n", WEXITSTATUS(status));}else {printf("wait child failed, return.\n");return 1;}}return 0;
}

让父进程做其他任务

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>#define TASK_NUM 10// 要保存的任务相关的
typedef void (*func_t)(); //?
func_t other_task[TASK_NUM] = {NULL};  //函数指针数组// 预设一批任务
void sync_disk()
{printf("这是一个刷新数据的任务!\n");
}
void sync_log()
{printf("这是一个同步日志的任务!\n");
}
void net_send()
{printf("这是一个进行网络发送的任务!\n");
}//将任务加载进任务列表
int LoadTask(func_t func)
{int i = 0;for(; i < TASK_NUM; i++){if(other_task[i] == NULL) break;}if(i == TASK_NUM) return -1;else other_task[i] = func;return 0;
}void InitTask()
{for(int i = 0; i < TASK_NUM; i++) other_task[i] = NULL;LoadTask(sync_disk);LoadTask(sync_log);LoadTask(net_send);
}void RunTask()
{for(int i = 0; i < TASK_NUM; i++){if(other_task[i] == NULL) continue;other_task[i]();}
}int main()
{pid_t id = fork();if(id == 0){//子进程int cnt = 50;while(cnt){printf("我是子进程,我还活着呢,我还有%dS, pid: %d, ppid%d\n", cnt--, getpid(), getppid());sleep(1);//int *p = NULL;//*p = 100;}exit(111);}InitTask();// 父进程//pid_t ret_id = wait(NULL);while(1){int status = 0;pid_t ret_id = waitpid(id, &status, WNOHANG); // 夯住了if(ret_id < 0){printf("waitpid error!\n");exit(1);}else if(ret_id == 0){RunTask();sleep(1);continue;}else{if(WIFEXITED(status)) // 是否收到信号{printf("wait success, child exit code: %d\n", WEXITSTATUS(status));}else{printf("wait success, child exit signal: %d\n", status & 0x7F);}// printf("我是父进程,等待子进程成功, pid: %d, ppid: %d, ret_id: %d, child exit code: %d, child exit signal: %d\n",\//     getpid(), getppid(), ret_id, (status>>8)&0xFF, status & 0x7F);break;}}

相关文章:

【Linux】进程等待

文章目录 进程等待进程等待必要性实验(见见猪跑)进程等待的方法wait方法waitpid**方法**宏的使用方法获取子进程status 阻塞VS非阻塞概念对比非阻塞有什么好处 具体代码实现进程的阻塞等待方式:进程的非阻塞等待方式:让父进程做其他任务 进程等待 进程等待必要性 之前讲过&am…...

电视「沉浮录」:跌出家电“三大件”?

【潮汐商业评论/原创】 “这年头谁还看电视&#xff0c;家里电视近一年都没打开过了&#xff0c;我明天就打算把它二手卖掉。”想到已落灰许久的电视机&#xff0c;Andy打开了二手平台。 “要不是这几年孩子网课多&#xff0c;我是真没考虑换新电视&#xff0c;家里用了8年的…...

前端实现调用打印机和小票打印(TSPL )功能

Ⅰ- 壹 - 使用需求 前端 的方式 点击这个按钮&#xff0c;直接让打印机打印我想要的东西 Ⅱ - 贰 - 小票打印 目前比较好的方式就是直接用 TSPL 标签打印指令集, 基础环境就不多说了,这个功能的实现就是利用usb发送指令,现在缺少个来让我们能够和usb沟通的工具,下面这就是推…...

串口通信(6)应用定时器中断+串口中断实现接收一串数据

本文为博主 日月同辉&#xff0c;与我共生&#xff0c;csdn原创首发。希望看完后能对你有所帮助&#xff0c;不足之处请指正&#xff01;一起交流学习&#xff0c;共同进步&#xff01; > 发布人&#xff1a;日月同辉,与我共生_单片机-CSDN博客 > 欢迎你为独创博主日月同…...

【WinForm详细教程六】WinForm中的GroupBox和Panel 、TabControl 、SplitContainer控件

文章目录 1.GroupBox和Panel2.TabControl3.SplitContainer 1.GroupBox和Panel GroupBox&#xff1a;是一个分组容器&#xff0c;提供一个框架将相关的控件组织在一起&#xff0c;它有标题、边框&#xff0c;但没有滚动条。 Panel&#xff1a;也是一个容器控件&#xff0c;用来…...

gradle与maven

Gradle 和 Maven 都是流行的构建工具&#xff0c;通常用于构建和管理 Java 和 Android 项目。它们都可以自动下载依赖库、编译代码、运行测试、打包和发布等。 以下是对 Gradle 和 Maven 的介绍&#xff1a; Gradle&#xff1a; Gradle 是一个基于 Groovy 和 Kotlin 的构建自…...

2.Docker基本架构简介与安装实战

1.认识Docker的基本架构 下面这张图是docker官网上的&#xff0c;介绍了整个Docker的基础架构&#xff0c;我们根据这张图来学习一下docker的涉及到的一些相关概念。 1.1 Docker的架构组成 Docker架构是由Client(客户端)、Docker Host(服务端)、Registry(远程仓库)组成。 …...

拓世法宝 | 数字经济崛起,美业如何抓住流量风口?

爱美之心&#xff0c;人皆有之。无论男女&#xff0c;都会很自然地对美好事物燃起兴致&#xff0c;跟高颜值相关的事物总能聚集注意力。例如直播平台里的美女网红收割流量赚得盆满钵满&#xff0c;面庞俊俏的年轻偶像吸引万千粉丝&#xff0c;还有“央视最美记者”王冰冰、“最…...

Scala 泛型编程

1. 泛型 Scala 支持类型参数化&#xff0c;使得我们能够编写泛型程序。 1.1 泛型类 Java 中使用 <> 符号来包含定义的类型参数&#xff0c;Scala 则使用 []。 class Pair[T, S](val first: T, val second: S) {override def toString: String first ":" sec…...

索引失效的场景有哪些?

虽然你这列上建了索引&#xff0c;查询条件也是索引列&#xff0c;但最终执行计划没有走它的索引。下面是引起这种问题的几个关键点。 列与列对比 某个表中&#xff0c;有两列&#xff08;id和c_id&#xff09;都建了单独索引&#xff0c;下面这种查询条件不会走索引 select…...

Java进阶04 final关键字、abstract抽象、interface接口、JDK8与JDK9中接口的区别、内部类和匿名类

文章目录 一、final关键字二、abstract关键字三、接口interface四、JDK8和JDK9中接口的区别五、内部类 一、final关键字 final可以修饰类、方法、变量 用final修饰类 表示此类不能被继承 用final修饰方法 表示方法不可以被重写 用final修饰变量 既可以修饰成员变量也可以修饰…...

Python的web自动化学习(五)Selenium的隐式等待(元素定位)

引言&#xff1a; WebDriver隐式等待是一种全局性的等待方式&#xff0c;它会在查找元素时设置一个固定的等待时间。当使用隐式等待时&#xff0c;WebDriver会在查找元素时等待一段时间&#xff0c;如果在等待时间内找到了元素&#xff0c;则立即执行下一步操作&#xff1b;如果…...

20231102从头开始配置cv180zb的编译环境(欢迎入坑,肯定还有很多问题等着你)

20231102从头开始配置cv180zb的编译环境&#xff08;欢迎入坑&#xff0c;肯定还有很多问题等着你&#xff09; 2023/11/2 11:31 &#xff08;欢迎入坑&#xff0c;本篇只是针对官方的文档整理的&#xff01;只装这些东西你肯定编译不过的&#xff0c;还有很多问题等着你呢&…...

CentOS 安装HTTP代理服务器 Squid

参考&#xff1a;大部分摘自此文&#xff0c;做了少部分修改 Squid 是一个功能全面的缓存代理服务器&#xff0c;它支持著名的网络协议像 HTTP&#xff0c;HTTPS&#xff0c;FTP 等等。将 Squid 放在网页服务器的前端&#xff0c;通过缓存重复请求&#xff0c;过滤网络流量等&…...

ubuntu下开发提效的小tips

一、常用操作使用简写的别名&#xff0c;写进bashrc文件中 背景&#xff1a;经常需要cd至某个文件夹中&#xff0c;然后再执行对应的操作&#xff1b;写进bashrc文件中后&#xff0c;可以直接用缩略命令替代这一连串的命令&#xff1b; 用到的工具&#xff1a; 设置命令别名a…...

Java反射详解:入门+使用+原理+应用场景

反射非常强大和有用&#xff0c;现在市面上绝大部分框架(spring、mybatis、rocketmq等等)中都有反射的影子&#xff0c;反射机制在框架设计中占有举足轻重的作用。 所以&#xff0c;在你Java进阶的道路上&#xff0c;你需要掌握好反射。 怎么才能学好反射&#xff0c;我们需要…...

PostgreSQL 工具的相关介绍

1.1 psql工具 psql是PostgreSQL中的一个命令行交互式客户端工具&#xff0c;类似 Oracle中的命令行工具sqlplus&#xff0c;它允许用户交互地键入SQL语句或命 令&#xff0c;然后将其发送给PostgreSQL服务器&#xff0c;再显示SQL语句或命令的结 果。 1.2 psql的简单使用 使用…...

结合组件库实现table组件树状数据的增删改

如图所示&#xff0c;可以实现树状数据的新增子项&#xff0c;新增平级&#xff0c;删除。主要用到了递归 代码&#xff1a; <template><el-table :data"tableData" style"width: 100%; margin-bottom: 20px" row-key"id" border def…...

Microsoft 365 管理自动化

Microsoft 365 服务被大多数组织广泛使用&#xff0c;每天生成的数据量巨大。解决 Microsoft 365 中的问题可能非常困难&#xff0c;并且使用多个管理中心来保护组织变得复杂。本机控制台还缺少某些批量管理任务、全面的审计报告和基于角色的精细访问控制。 Microsoft 360 管理…...

unraid 安装并设置 zerotier 内网穿透安装 unraid 局域网内其他设备

Read Original 最近看了以下两个文章&#xff0c;感谢发布的各种精彩文章&#xff0c;让我受益匪浅。OPENWRT 的固件在设置了&#xff0c;【自动允许客户端 NAT】后&#xff0c;可以直接访问局域网其他设备&#xff0c;而我 unraid 部署 zerotier 后&#xff0c;只能访问 unra…...

SVN 报错 Previous operation has not finished,提示需要 clean up

SVN报错"Previous operation has not finished"通常是由于操作中断导致工作副本被锁定。 解决方法按优先级推荐&#xff1a; 1&#xff09;首选执行Cleanup操作&#xff0c;勾选"Cleanup working copy status"和"Break write locks"&#xff1b;…...

5分钟免费解决NVIDIA显卡显示器色彩过饱和的终极方案

5分钟免费解决NVIDIA显卡显示器色彩过饱和的终极方案 【免费下载链接】novideo_srgb Calibrate monitors to sRGB or other color spaces on NVIDIA GPUs, based on EDID data or ICC profiles 项目地址: https://gitcode.com/gh_mirrors/no/novideo_srgb 你是否曾经发现…...

深度解析碧蓝航线自动化脚本:架构设计与智能调度创新

深度解析碧蓝航线自动化脚本&#xff1a;架构设计与智能调度创新 【免费下载链接】AzurLaneAutoScript Azur Lane bot (CN/EN/JP/TW) 碧蓝航线脚本 | 无缝委托科研&#xff0c;全自动大世界 项目地址: https://gitcode.com/gh_mirrors/az/AzurLaneAutoScript 在移动游戏…...

智能消费记账|基于SSM+vue的大学生智能消费记账系统(源码+数据库+文档)

智能消费记账系统 目录 基于SSMvue的大学生智能消费记账系统 一、前言 二、系统设计 三、系统功能设计 1 用户列表 2 预算信息管理 3 预算类型管理 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#x…...

终极指南:3步实现Switch手柄在Windows PC上的完美XInput兼容

终极指南&#xff1a;3步实现Switch手柄在Windows PC上的完美XInput兼容 【免费下载链接】BetterJoy Allows the Nintendo Switch Pro Controller, Joycons and SNES controller to be used with CEMU, Citra, Dolphin, Yuzu and as generic XInput 项目地址: https://gitcod…...

ERC-1155终极指南:统一数字资产管理的未来标准

ERC-1155终极指南&#xff1a;统一数字资产管理的未来标准 引言 在数字资产爆炸式增长的时代&#xff0c;管理游戏道具、数字藏品和供应链凭证等多样化的资产&#xff0c;常常需要部署多个独立的智能合约&#xff0c;导致成本高昂且操作繁琐。有没有一种方案能“一合约统管万…...

金属3D打印光束整形:两大路线正面PK

作为金属3D打印技术的最新发展&#xff0c;开展光束整形技术研究的企业越来越多&#xff0c;研发的进程也越来越深。3D打印技术参考注意到&#xff0c;国外由EOS引领该技术发展&#xff0c;同时还有Aconity3D和DMG Mori等行业领导者&#xff1b;在国内&#xff0c;铂力特、华曙…...

Python报错Resource averaged_perceptron_tagger_eng not found

用python标注英文单词词形时&#xff0c;报错&#xff1a; import nltk nltk.download(‘averaged_perceptron_tagger_eng’) Resource averaged_perceptron_tagger_eng not found. 估计是因为网络问题&#xff0c;遂改用离线安装的方式。 第一步&#xff1a;下载averaged_perc…...

实战指南:如何将SPIN的超像素思想,迁移到你的图像修复项目里(附思路)

超像素注意力机制在图像修复中的工程实践指南 当你在处理一张模糊的老照片时&#xff0c;是否曾为那些无法辨认的面部细节而苦恼&#xff1f;或者在增强低分辨率监控画面时&#xff0c;发现传统方法总是让边缘变得生硬不自然&#xff1f;这些问题背后&#xff0c;隐藏着一个被大…...

用P4和BMv2在Ubuntu上快速搭建一个可编程三层交换机(附完整代码和避坑指南)

用P4和BMv2在Ubuntu上构建可编程交换机的实战指南 当传统网络设备无法满足灵活的业务需求时&#xff0c;P4语言正在重新定义网络数据平面的可能性。想象一下&#xff0c;你可以在30分钟内将一台普通Ubuntu机器变成支持自定义转发逻辑的三层交换机——这正是P4带来的变革力量。本…...