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

死锁 手撕死锁检测工具

目录

引言

一.理论联立

1.死锁的概念和原因

2.死锁检测的基本思路

 

3.有向图在死锁检测中的应用

二.代码实现案例(我们会介绍部分重要接口解释)

1.我们定义一个线性表来存线程ID和锁ID

2.表中数据的查询接口

3.表中数据的删除接口

4.表中数据的添加接口

5.before_lock接口

6.afterlock接口

7.after_unlock接口

8.加锁和解锁的接口

9.检测死锁的接口

三.结果展示


 

引言

死锁是指在计算机系统中,多个进程(或线程)因竞争资源而造成的一种僵局,若无外力作用,这些进程(或线程)都将无法向前推进

我们将基于多个线程和多个互斥锁来介绍死锁的长生

 

一.理论联立

1.死锁的概念和原因

①.死锁是操作系统和学术概念,指线程占用资源导致互相等待对方释放资源的情况。

②.死锁常见于多线程环境中,导致CPU占用率100%,出现死循环。

图中有线程A,线程B,和线程C 三个线程 它们各自拥有自己各自的资源的情况下 其中

线程A想占用线程B的资源

线程B 想占用线程C的资源

线程C想占用线程A的资源 

最后形成了一个环 最后导致了死锁

2.死锁检测的基本思路

①.死锁检测依赖于资源占用情况的检测,通过判断是否构成环来实现。

②.环的构成表示线程间形成了死锁。

 

3.有向图在死锁检测中的应用

①.有向图是否成环的问题是死锁检测的底层算法。

②.通过有向图来判断是否构成环,从而检测死锁。

③.有向图的构建通过节点和边来表示线程和资源的关系。

④.环的检测通过深度优先搜索(DFS)来实现。

 

 

 

二.代码实现案例(我们会介绍部分重要接口解释)

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#define _GNU_SOURCE
#include <dlfcn.h>
#include<stdlib.h>#define MAX		100typedef unsigned long int uint64;struct rela_node{pthread_mutex_t *mtx;pthread_t thid;
};struct rela_node rela_table[MAX]={0};//search
pthread_t search_rela_table(pthread_mutex_t*mtx){int i = 0;for(i;i<MAX;i++){if(mtx==rela_table[i].mtx){return rela_table[i].thid;}}return 0;
}//dele
int dele_rela_table(pthread_t tid,pthread_mutex_t *mtx){int i =0;for(i;i<MAX;i++){if((rela_table[i].thid==tid)&&(rela_table[i].mtx==mtx)){rela_table[i].thid=0;rela_table[i].mtx=NULL;return 0;}}return -1;
}//add
int add_rela_table(pthread_t tid,pthread_mutex_t *mtx){int i =0;for(i;i<MAX;i++){if((rela_table[i].thid==0)&&(rela_table[i].mtx==NULL)){rela_table[i].thid=tid;rela_table[i].mtx=mtx;return 0;}}return -1;
}#if 1  
//有向图
enum Type {PROCESS, RESOURCE};struct source_type {uint64 id;enum Type type;uint64 lock_id;int degress;
};struct vertex {struct source_type s;struct vertex *next;};struct task_graph {struct vertex list[MAX];int num;struct source_type locklist[MAX];int lockidx; //pthread_mutex_t mutex;
};struct task_graph *tg = NULL;
int path[MAX+1];
int visited[MAX];
int k = 0;
int deadlock = 0;struct vertex *create_vertex(struct source_type type) {struct vertex *tex = (struct vertex *)malloc(sizeof(struct vertex ));tex->s = type;tex->next = NULL;return tex;}int search_vertex(struct source_type type) {int i = 0;for (i = 0;i < tg->num;i ++) {if (tg->list[i].s.type == type.type && tg->list[i].s.id == type.id) {return i;}}return -1;
}void add_vertex(struct source_type type) {if (search_vertex(type) == -1) {tg->list[tg->num].s = type;tg->list[tg->num].next = NULL;tg->num ++;}}int add_edge(struct source_type from, struct source_type to) {add_vertex(from);add_vertex(to);struct vertex *v = &(tg->list[search_vertex(from)]);while (v->next != NULL) {v = v->next;}v->next = create_vertex(to);}int verify_edge(struct source_type i, struct source_type j) {if (tg->num == 0) return 0;int idx = search_vertex(i);if (idx == -1) {return 0;}struct vertex *v = &(tg->list[idx]);while (v != NULL) {if (v->s.id == j.id) return 1;v = v->next;}return 0;}int remove_edge(struct source_type from, struct source_type to) {int idxi = search_vertex(from);int idxj = search_vertex(to);if (idxi != -1 && idxj != -1) {struct vertex *v = &tg->list[idxi];struct vertex *remove;while (v->next != NULL) {if (v->next->s.id == to.id) {remove = v->next;v->next = v->next->next;free(remove);break;}v = v->next;}}}void print_deadlock(void) {int i = 0;printf("cycle : ");for (i = 0;i < k-1;i ++) {printf("%ld --> ", tg->list[path[i]].s.id);}printf("%ld\n", tg->list[path[i]].s.id);}int DFS(int idx) {struct vertex *ver = &tg->list[idx];if (visited[idx] == 1) {path[k++] = idx;print_deadlock();deadlock = 1;return 0;}visited[idx] = 1;path[k++] = idx;while (ver->next != NULL) {DFS(search_vertex(ver->next->s));k --;ver = ver->next;}return 1;}int search_for_cycle(int idx) {struct vertex *ver = &tg->list[idx];visited[idx] = 1;k = 0;path[k++] = idx;while (ver->next != NULL) {int i = 0;for (i = 0;i < tg->num;i ++) {if (i == idx) continue;visited[i] = 0;}for (i = 1;i <= MAX;i ++) {path[i] = -1;}k = 1;DFS(search_vertex(ver->next->s));ver = ver->next;}}int init_graph(void){tg=(struct task_graph*)malloc(sizeof(struct task_graph));tg->num=0;
}#endifvoid before_lock(pthread_t tid,pthread_mutex_t*mtx){pthread_t otherid=search_rela_table(mtx);if(otherid!=0){//mtx有线程在占用struct source_type from;from.id=tid;from.type=PROCESS;struct source_type to;to.id=otherid;to.type=PROCESS;add_edge(from,to);}}//如果走到after_lock 则表明mtx没有被线程占用 把之前的边删除 然后我们占用该mtx
void after_lock(pthread_t tid,pthread_mutex_t*mtx){pthread_t otherid=search_rela_table(mtx);if(otherid!=0){//删除旧边struct source_type from;from.id=tid;from.type=PROCESS;struct source_type to;to.id=otherid;to.type=PROCESS;if(verify_edge(from,to)){remove_edge(from,to);}}//mtx无线程在占用 则占我们占用add_rela_table(tid,mtx);
}
void after_unlock(pthread_t tid,pthread_mutex_t*mtx){dele_rela_table(tid,mtx);//有小问题 这个死锁工具可能只能检测一次 如果存在解锁情况 可能会导致after_lock mtx找不到旧线程id
}//检测死锁
void check_dead_lock(void){int i =0;for(i;i<tg->num;i++){search_for_cycle(i);}
}static void *thread_routine(void*arg){while(1){sleep(5);check_dead_lock();}
}
void start_check(void) {pthread_t tid;pthread_create(&tid, NULL, thread_routine, NULL);}#if 1  //hook
typedef int (*pthread_mutex_lock_t)(pthread_mutex_t*mtx);
pthread_mutex_lock_t pthread_mutex_lock_f=NULL;typedef int (*pthread_mutex_unlock_t)(pthread_mutex_t*mtx);
pthread_mutex_unlock_t pthread_mutex_unlock_f=NULL;typedef int (*pthread_create_t)(pthread_t *restrict thread, const pthread_attr_t *restrict attr,void *(*start_routine)(void *), void *restrict arg);
pthread_create_t pthread_create_f = NULL;int pthread_mutex_lock(pthread_mutex_t*mtx){// printf("before pthread_mutex_lock%ld,%p \n",pthread_self(),mtx);pthread_t selfid = pthread_self();before_lock(selfid, mtx);pthread_mutex_lock_f(mtx);// printf("after pthread_mutex_lock\n");after_lock(selfid,mtx);
}int pthread_mutex_unlock(pthread_mutex_t*mtx){pthread_t selfid = pthread_self();pthread_mutex_unlock_f(mtx);after_unlock(selfid,mtx);// printf("after pthread_mutex_unlock%ld,%p \n",pthread_self(),mtx);}int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr,void *(*start_routine)(void *), void *restrict arg) {pthread_create_f(thread,attr,start_routine,arg);struct source_type v1;v1.id=*thread;v1.type=PROCESS;add_vertex(v1);
}void init_hook(void){if(!pthread_mutex_lock_f){pthread_mutex_lock_f = dlsym(RTLD_NEXT,"pthread_mutex_lock");}if(!pthread_mutex_unlock_f){pthread_mutex_unlock_f = dlsym(RTLD_NEXT,"pthread_mutex_unlock");}if (!pthread_create_f) {pthread_create_f = dlsym(RTLD_NEXT, "pthread_create");}}
#endif#if 1//debug
pthread_mutex_t mtx1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mtx2 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mtx3 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mtx4 = PTHREAD_MUTEX_INITIALIZER;void * t1_cb(void*arg){printf("pid1=%ld\n",pthread_self());pthread_mutex_lock(&mtx1);sleep(1);pthread_mutex_lock(&mtx2);pthread_mutex_unlock(&mtx2);pthread_mutex_unlock(&mtx1);}
void * t2_cb(void*arg){printf("pid2=%ld\n",pthread_self());pthread_mutex_lock(&mtx2);sleep(1);pthread_mutex_lock(&mtx3);pthread_mutex_unlock(&mtx3);pthread_mutex_unlock(&mtx2);}void * t3_cb(void*arg){printf("pid3=%ld\n",pthread_self());pthread_mutex_lock(&mtx3);sleep(1);pthread_mutex_lock(&mtx4);pthread_mutex_unlock(&mtx4);pthread_mutex_unlock(&mtx3);
}void * t4_cb(void*arg){printf("pid4=%ld\n",pthread_self());pthread_mutex_lock(&mtx4);sleep(1);pthread_mutex_lock(&mtx1);pthread_mutex_unlock(&mtx1);pthread_mutex_unlock(&mtx4);
}
int main(){init_graph();//有向图的初始化init_hook();//hook函数的初始化pthread_t t1,t2,t3,t4;start_check();//开始检测死锁pthread_create(&t1,NULL,t1_cb,NULL);pthread_create(&t2,NULL,t2_cb,NULL);pthread_create(&t3,NULL,t3_cb,NULL);pthread_create(&t4,NULL,t4_cb,NULL);pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_join(t3,NULL);pthread_join(t4,NULL);printf("complete\n");
}
#endif

具体代码接口的实现

1.我们定义一个线性表来存线程ID和锁ID

typedef unsigned long int uint64;struct rela_node{pthread_mutex_t *mtx;pthread_t thid;
};struct rela_node rela_table[MAX]={0};

2.表中数据的查询接口

//search
pthread_t search_rela_table(pthread_mutex_t*mtx){int i = 0;for(i;i<MAX;i++){if(mtx==rela_table[i].mtx){return rela_table[i].thid;}}return 0;
}

 

3.表中数据的删除接口

//dele
int dele_rela_table(pthread_t tid,pthread_mutex_t *mtx){int i =0;for(i;i<MAX;i++){if((rela_table[i].thid==tid)&&(rela_table[i].mtx==mtx)){rela_table[i].thid=0;rela_table[i].mtx=NULL;return 0;}}return -1;
}

4.表中数据的添加接口

//add
int add_rela_table(pthread_t tid,pthread_mutex_t *mtx){int i =0;for(i;i<MAX;i++){if((rela_table[i].thid==0)&&(rela_table[i].mtx==NULL)){rela_table[i].thid=tid;rela_table[i].mtx=mtx;return 0;}}return -1;
}

 

5.before_lock接口

void before_lock(pthread_t tid,pthread_mutex_t*mtx){pthread_t otherid=search_rela_table(mtx);if(otherid!=0){//mtx有线程在占用struct source_type from;from.id=tid;from.type=PROCESS;struct source_type to;to.id=otherid;to.type=PROCESS;add_edge(from,to);}}

我们传入当前线程id和锁

我们先对锁进行判断是否有线程在占用 如果有线程在占用我们则和该线程建立一条边

6.afterlock接口

//如果走到after_lock 则表明mtx没有被线程占用 把之前的边删除 然后我们占用该mtx
void after_lock(pthread_t tid,pthread_mutex_t*mtx){pthread_t otherid=search_rela_table(mtx);if(otherid!=0){//删除旧边struct source_type from;from.id=tid;from.type=PROCESS;struct source_type to;to.id=otherid;to.type=PROCESS;if(verify_edge(from,to)){remove_edge(from,to);}}//mtx无线程在占用 则占我们占用add_rela_table(tid,mtx);
}

我们要先判断该锁之前是否和其他线程建立边 如果有我们就删除旧边 然后占用该把锁

7.after_unlock接口

void after_unlock(pthread_t tid,pthread_mutex_t*mtx){dele_rela_table(tid,mtx);//有小问题 这个死锁工具可能只能检测一次 如果存在解锁情况 可能会导致after_lock mtx找不到旧线程id
}

8.加锁和解锁的接口

int pthread_mutex_lock(pthread_mutex_t*mtx){// printf("before pthread_mutex_lock%ld,%p \n",pthread_self(),mtx);pthread_t selfid = pthread_self();before_lock(selfid, mtx);pthread_mutex_lock_f(mtx);// printf("after pthread_mutex_lock\n");after_lock(selfid,mtx);
}int pthread_mutex_unlock(pthread_mutex_t*mtx){pthread_t selfid = pthread_self();pthread_mutex_unlock_f(mtx);after_unlock(selfid,mtx);// printf("after pthread_mutex_unlock%ld,%p \n",pthread_self(),mtx);}

9.检测死锁的接口

//检测死锁
void check_dead_lock(void){int i =0;for(i;i<tg->num;i++){search_for_cycle(i);}
}static void *thread_routine(void*arg){while(1){sleep(5);check_dead_lock();}
}
void start_check(void) {pthread_t tid;pthread_create(&tid, NULL, thread_routine, NULL);}

 

三.结果展示

 

相关文章:

死锁 手撕死锁检测工具

目录 引言 一.理论联立 1.死锁的概念和原因 2.死锁检测的基本思路 3.有向图在死锁检测中的应用 二.代码实现案例&#xff08;我们会介绍部分重要接口解释&#xff09; 1.我们定义一个线性表来存线程ID和锁ID 2.表中数据的查询接口 3.表中数据的删除接口 4.表中数据的添…...

软考高级-系统架构设计师 案例题-软件架构设计

文章目录 软件架构设计质量属性效用树&#xff0c;质量属性判断必背概念架构风格对比MVC架构J2EE四层结构面向服务架构SOA企业服务总线ESB历年真题【问题1】 &#xff08;12分)【问题2】&#xff08;13分&#xff09; 参考答案历年真题【问题1】&#xff08;12分&#xff09;【…...

JavaScript Date(日期)

JavaScript Date(日期) JavaScript的Date对象是处理日期和时间的一个强大工具。它允许开发者轻松地创建日期对象、格式化日期、计算日期差以及执行各种日期相关的操作。本文将深入探讨JavaScript中的Date对象,包括其创建、格式化、操作以及与其他日期时间的交互。 创建Dat…...

vue+d3js+fastapi实现天气柱状图折线图饼图

说明&#xff1a; vued3jsfastapi实现天气柱状图折线图饼图 效果图&#xff1a; step0:postman 1. 生成天气数据&#xff08;POST请求&#xff09;&#xff1a;URL: http://localhost:8000/generate-data/?year2024&month3&seed42 方法: POST Headers:Content-Type:…...

vue:前端预览 / chrome浏览器设置 / <iframe> 方法预览 doc、pdf / vue-pdf 预览pdf

一、本文目标 <iframe> 方法预览 pdf 、word vue-pdf 预览pdf 二、<iframe> 方法 2.1、iframe 方法预览需要 浏览器 设置为&#xff1a; chrome&#xff1a;设置-隐私设置和安全性-网站设置-更多内容设置-PDF文档 浏览器访问&#xff1a; chrome://settings/co…...

【NLP 56、实践 ⑬ LoRA完成NER任务】

目录 一、数据文件 二、模型配置文件 config.py 三、数据加载文件 loader.py 1.导入文件和类的定义 2.初始化 3.数据加载方法 代码运行流程 4.文本编码 / 解码方法    ① encode_sentence()&#xff1a; ② decode()&#xff1a; 代码运行流程 ③ padding()&#xff1a; 代码…...

Java性能调优2025:从JVM到Kubernetes的全链路优化策略

摘要 &#x1f4dd; 本文将带你深入探讨2025年Java全链路性能调优的最新实践&#xff0c;从JVM底层优化到Kubernetes集群调优&#xff0c;涵盖GC策略选择、JIT优化、容器化最佳实践等核心内容。通过大量实践案例和代码示例&#xff0c;帮助你构建完整的性能优化知识体系。 目…...

【力扣hot100题】(076)买卖股票的最佳时机

终于来到了最考验智商的贪心算法。 之前做过&#xff0c;但花了不少时间思考&#xff0c;所以这次做的很快。 思路就是记录最小价格&#xff0c;然后一路遍历边调整新的最小价格边比较目前价格和最小价格差价。 class Solution { public:int maxProfit(vector<int>&am…...

Java基础 4.9

1.方法递归调用练习 //请使用递归的方式求出斐波那契数1, 1, 2, 3, 5, 8, 13 //给你一个整数n, 求出它的值是多少 /* 思路 n 1 1 n 2 1 n > 3 前两个数的和 递归的思路 */ public class RecursionExercise01 {public static void main(String[] args) {Mathod mathod ne…...

NDK开发:音视频处理基础

音视频处理基础 一、音视频基础 1.1 音视频基本概念 视频编码格式 H.264/AVCH.265/HEVCVP8/VP9AV1音频编码格式 AACMP3PCMOPUS封装格式 MP4FLVMKVTS1.2 音视频处理流程 视频处理流程 采集(Camera/Screen)预处理(美颜/滤镜)编码(H.264/H.265)封装传输/存储音频处理流程 …...

WPF 组件的宽高绑定另一个组件的宽高的指定比值

0.此方法比较适用于响应式界面,组件的大小需要根据窗体大小改变。 1.创建转换函数,并传入比值 public class SizeConverter : IValueConverter{public object Convert(object value, Type targetType, object parameter, CultureInfo culture){if (value is double d &&…...

c#的form实现叠叠乐游戏

说明&#xff1a; 我希望用c#的form实现叠叠乐的游戏&#xff0c;玩家需要堆叠方块来建造高塔。 效果图&#xff1a; step1:游戏规则 游戏实现步骤&#xff1a; a. 处理事件&#xff0c;玩家可以释放摆动的方块&#xff0c;方块会下落。 b. 更新摆动方块的位移&#xff0c;根…...

k8s 1.23升级1.24

0、简介 这里只用3台服务器来做一个简单的集群&#xff0c;当前版本是1.23.17目标升级到1.24.17 地址主机名192.168.160.40kuber-master-1192.168.160.41kuber-master-2192.168.160.42kuber-node-1 我这里设置的master2可调度pod&#xff0c;将master2的污点去掉 kubectl de…...

Qt中的元对象系统

Qt的元对象系统(Meta-Object System)提供了对象间通信的信号和槽机制、运行时类型信息和动态属性系统。 元对象系统基于以下三个方面&#xff1a; (1).QObject类&#xff1a;为可以利用元对象系统的对象提供了基类。 (2).Q_OBJECT宏&#xff1a;用于启用元对象功能&#xff0c;…...

qt之opengl使用

使用qt中的openglWidget绘制一个三角形。自定义的类继承关系sunOpengl : public QOpenGLWidget,QOpenGLFunctions_3_3_Core 代码如下 /*----MainWindow.cpp----------------------------------------------*/ #include "mainwindow.h" #include "./ui_mainwin…...

晋城市电子健康证上传照片尺寸要求及手机拍照制作方法

晋城市餐饮从业人员健康证电子照片上传有着明确的技术规范。根据"晋城市从业人员电子健康证明服务平台"要求&#xff0c;照片尺寸应为358像素&#xff08;宽&#xff09;441像素&#xff08;高&#xff09;&#xff0c;这一比例符合标准证件照的规格。照片底色可选择…...

python基础语法11-文件读写

在 Python 中&#xff0c;文件操作是日常编程中的常见任务之一。Python 提供了简单且强大的工具来读取和写入文件。通过使用内置的 open() 函数、read()、readline()、write() 等方法&#xff0c;我们可以轻松实现对文件的操作。此外&#xff0c;Python 的 with 语句可以帮助我…...

js实现跨域下载,展示下载进度以及自定义下载名称功能

一、 下载进度 loading弹窗结构 // loading状态DOM function setLoading() {let content document.querySelector(.loading)content.innerHTML content.innerHTML <div class"loading_content"><div class"contentBox"><div class&quo…...

MCP 实战系列(Day 2)- 动手搓个文件系统 MCP 服务器

上期回顾&#xff1a;MCP 实战系列&#xff08;Day 1&#xff09;- 什么是 MCP&#xff1f; 在上期文章中&#xff0c;我们详细介绍了 Model Context Protocol&#xff08;MCP&#xff09;的基本概念和应用场景。本节将带领大家开发一个简易的 Filesystem MCP Server&#xff…...

LabVIEW运动控制(三):EtherCAT运动控制器的高效加工指令自定义封装

ZMC408CE 高性能总线型运动控制器 ZMC408CE是正运动推出的一款多轴高性能EtherCAT总线运动控制器&#xff0c;具有EtherCAT、EtherNET、RS232、CAN和U盘等通讯接口&#xff0c;ZMC系列运动控制器可应用于各种需要脱机或联机运行的场合。 ZMC408CE支持PLC、Basic、HMI组态三种编…...

Java接口性能优化面试问题集锦:高频考点与深度解析

1. 如何定位接口性能瓶颈&#xff1f;常用哪些工具&#xff1f; 考察点&#xff1a;性能分析工具的使用与问题定位能力。 核心答案&#xff1a; 工具&#xff1a;Arthas&#xff08;在线诊断&#xff09;、JProfiler&#xff08;内存与CPU分析&#xff09;、VisualVM、Prometh…...

Xilinx虚拟输入/输出(VIO)IP核详细介绍及使用示例

LogiCORE™ IP虚拟输入/输出(VIO)内核是一款可定制化的内核,能够实时监控和驱动FPGA(现场可编程门阵列)内部信号。其输入和输出端口的数量及位宽均可根据需求定制,以便与FPGA设计进行接口对接。由于VIO内核与被监控和/或驱动的设计保持同步,因此应用于您设计中的所有设计…...

Vue3+Vite+TypeScript+Element Plus开发-09.登录成功跳转主页

系列文档目录 Vue3ViteTypeScript安装 Element Plus安装与配置 主页设计与router配置 静态菜单设计 Pinia引入 Header响应式菜单缩展 Mockjs引用与Axios封装 登录设计 登录成功跳转主页 多用户动态加载菜单 Pinia持久化 动态路由-配置 文章目录 目录 系列文档目…...

网络安全应急响应-启动项和任务计划排查

应急响应-启动项排查 在应急响应排查中&#xff0c;启动项和任务计划是攻击者常用的持久化手段。以下是对Windows和Linux系统的详细排查指南&#xff0c;涵盖基础步骤及扩展注意事项&#xff1a; 一、启动项排查 Windows系统 系统配置&#xff08;msconfig&#xff09; 运行 …...

Linux : 内核中的信号捕捉

目录 一 前言 二 信号捕捉的方法 1.sigaction()​编辑 2. sigaction() 使用 三 可重入函数 四 volatile 关键字 一 前言 如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。在Linux: 进程信号初识-CSDN博客 这一篇中已经学习到了一种信号…...

开发效率提升200%——cursor

cursor带来的编程"革命" 高级语言编程转为"自然语言编程"借助cursor&#xff0c;直接超越初级后台开发、超越初级前端开发、超越初级测试、超越初级UI&#xff0c;产研一体linux命令只用学不用记&#xff0c;语言描述就是命令给一个表结构流程提示词&…...

微软庆祝它成立整整50周年

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

SpringBoot 整合 MCP

SpringBoot 整合 MCP MCP MCP 协议主要分为&#xff1a; Client 客户端&#xff08;一般就是指 openai&#xff0c;deepseek 这些大模型&#xff09;Server 服务端&#xff08;也就是我们的业务系统&#xff09;我们要做的就是把我们存量系统配置成 MCP Server 环境 JDK17…...

【详细】MySQL 8 安装解压即用 (包含MySQL 5 卸载)

卸载MySQL 1.卸载 2.安装目录删除残余文件&#xff08;当初安装的位置&#xff09; 3.删除programData下面的mysql数据文件 4.检查mysql服务是否存在&#xff0c;如果存在则删除&#xff08;先暂停mysql服务&#xff09; sc delete mysql 5.删除注册表中残留信息 安装MySQL 8&…...

显示器各类异常处理方法

显示器各类异常处理方法 导航 文章目录 显示器各类异常处理方法导航画面无显示/黑屏/无HDMI信号输入显示器闪烁显示器花屏显示画面模糊或扭曲显示器颜色异常显示器出现死点或亮点 画面无显示/黑屏/无HDMI信号输入 ​ 首先应该检查的是显示器电源&#xff08;真的有人弄掉电源…...