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

死锁问题分析和解决——资源回收时

1.描述问题

在完成线程池核心功能功能时,没有遇到太大的问题(Any,Result,Semfore的设计),在做线程池资源回收时,遇到了死锁的问题        

1、在ThreadPool的资源回收,等待线程池所有线程退出时,发生死锁问题,导致进程无法退出

死锁代码:

#include "threadpool.h"#include <thread>
#include <iostream>const int TASK_MAX_THRESHHOLD = INT32_MAX;
const int THREAD_MAX_THRESHHOLD = 100;
const int THREAD_MAX_IDLE_TIME = 60;//单位:秒//线程池构造
ThreadPool::ThreadPool(): initThreadSize_(0), taskSize_(0), idleThreadSize_(0)//刚开始时还没有线程, curThreadSize_(0), taskQueMaxThreshHold_(TASK_MAX_THRESHHOLD), threadSizeThreshHold_(THREAD_MAX_THRESHHOLD), poolMode_(PoolMode::MODE_FIXED), isPoolRunning_(false)
{}//线程池析构
ThreadPool::~ThreadPool()
{isPoolRunning_ = false;notEmpty_.notify_all();//等待线程池里面所有的线程返回  有两种状态:阻塞 & 正在执行任务中std::unique_lock<std::mutex> lock(taskQueMtx_);exitCond_.wait(lock, [&]()->bool {return threads_.size() == 0; });
}//设置线程池的工作模式
void ThreadPool::setMode(PoolMode mode)
{if (checkRunningState())return;poolMode_ = mode;
}// 设置task任务队列上限阈值
void ThreadPool::setTaskQueMaxThreshHold(int threshhold)
{if (checkRunningState())return;taskQueMaxThreshHold_ = threshhold;
}//设置线程池cached模式下线程阈值
void ThreadPool::setThreadSizeThreshHold(int threshhold)
{if (checkRunningState())return;if (poolMode_ == PoolMode::MODE_CACHED){threadSizeThreshHold_ = threshhold;}
}// 给线程池提交任务  用户调用该接口,传入任务对象,生产任务
Result ThreadPool::submitTask(std::shared_ptr<Task> sp)
{//获取锁std::unique_lock<std::mutex> lock(taskQueMtx_);//线程的通信  等待任务队列有空余// 用户提交任务,最长不能阻塞超过1s,否则判断提交任务失败,返回//while (taskQue_.size() == taskQueMaxThreshHold_)//{//	notFull_.wait(lock);//}/** wait:直到等待满足条件(第二个参数lamada)才返回* wait_for:满足条件返回真,到了约定的时间段(5s)返回假* wait_until:满足条件返回真,到了约定的时间点(下周一)返回假*/if (!notFull_.wait_for(lock, std::chrono::seconds(1),[&]()->bool {return taskQue_.size() < (size_t)taskQueMaxThreshHold_; }))//等同于上面的语句,参数:需要释放的锁  函数对象(要能满足条件变量)//任务队列中的任务数小于上限的阈值,否则就阻塞在这句{//表示notFull_等待1s,条件依然没有满足std::cerr << "task queue is full,submit task fail." << std::endl;//return task->getResult(); //Task  Result  线程执行完task,task对象就被析构掉了return Result(sp, false);//返回临时对象,应该自动匹配右值的资源转移,如果编译不通过,把C++标准调高一点}//如果有空余,把任务放入任务队列中taskQue_.emplace(sp);taskSize_++;//因为新放了任务,任务队列肯定不空了,在notEmpty_上进行通知,赶快分配线程执行任务notEmpty_.notify_all();//cached模式 任务处理比较紧急 场景:小而快的任务 需要根据任务数量和空闲线程的数量,判断是否需要创建新的线程出来if (poolMode_ == PoolMode::MODE_CACHED&& taskSize_ > idleThreadSize_&& curThreadSize_ < threadSizeThreshHold_){std::cout << ">>> create new thread..." << std::this_thread::get_id() << " exit!" << std::endl;//创建新的线程对象auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::threadFunc, this, std::placeholders::_1));int threadId = ptr->getId();threads_.emplace(threadId, std::move(ptr));//threads_.emplace_back(std::move(ptr));//启动线程threads_[threadId]->start();//修改线程个数相关的变量curThreadSize_++;idleThreadSize_++;}//返回任务的Result对象return Result(sp);// return task->getResult();
}//开启线程池
void ThreadPool::start(int initThreadSize)
{//设置线程池的运行状态isPoolRunning_=true;//记录初始线程个数initThreadSize_ = initThreadSize;curThreadSize_ = initThreadSize;//创建线程对象for (int i = 0; i < initThreadSize_; i++){//创建thread线程对象的时候,把线程函数给到thread线程对象auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::threadFunc, this, std::placeholders::_1));int threadId = ptr->getId();threads_.emplace(threadId, std::move(ptr));//threads_.emplace_back(std::move(ptr));//unique_ptr将左值引用的拷贝构造和赋值都delete了,需要右值(进行资源转移)}//启动所有线程 std::vector<Thread*> threads_;for (int i = 0; i < initThreadSize_; i++){threads_[i]->start(); //需要去执行一个线程函数idleThreadSize_++;//记录初始空闲线程的数量}
}//定义线程函数  线程池的所有线程从任务队列里面消费任务
void ThreadPool::threadFunc(int threadid) //线程函数返回,相应的线程也就结束了
{/*std::cout << "begin threadFunc tid:" << std::this_thread::get_id() << std::endl;std::cout << "end threadFunc tid:" << std::this_thread::get_id() << std::endl;*/auto lastTime = std::chrono::high_resolution_clock().now();while (isPoolRunning_){std::shared_ptr<Task> task;{//先获取锁std::unique_lock<std::mutex> lock(taskQueMtx_);std::cout << "tid:" << std::this_thread::get_id()<< "尝试获取任务..." << std::endl;//cached模式下,有可能已经创建了很多的线程,但是空闲时间超过60s,应该把多余的线程结束回收掉(超过initThreadSize_数量的线程要进行回收)//当前时间-上一次线程执行的时间>60s//每一秒中返回一次  怎么区分:超时返回?还是有任务待执行返回while (taskQue_.size() == 0){if (poolMode_ == PoolMode::MODE_CACHED){//条件变量,超时返回了if (std::cv_status::timeout == notEmpty_.wait_for(lock, std::chrono::seconds(1))){auto now = std::chrono::high_resolution_clock().now();auto dur = std::chrono::duration_cast<std::chrono::seconds>(now - lastTime);if (dur.count() >= THREAD_MAX_IDLE_TIME && curThreadSize_ > initThreadSize_){//开始回收当前线程//记录线程数量的相关变量的值修改//把线程对象从线程列表容器中删除  没有办法  threadFunc  <=>thread对象//threadid=>thread对象=》删除threads_.erase(threadid);// 这个id不是std::this_thread::getid()  是自己生成的,我们自定义的curThreadSize_--;idleThreadSize_--;std::cout << "threadid:" << std::this_thread::get_id() << "exit!" << std::endl;return;}}}else{//等待notEmpty条件notEmpty_.wait(lock);}//线程池结束,回收线程资源if (!isPoolRunning_){threads_.erase(threadid);// 这个id不是std::this_thread::getid()  是自己生成的,我们自定义的std::cout << "threadid:" << std::this_thread::get_id() << "exit!" << std::endl;exitCond_.notify_all();return;}}idleThreadSize_--;//唤醒线程工作,空闲线程-1std::cout << "tid:" << std::this_thread::get_id()<< "获取任务成功..." << std::endl;//从任务队列中取一个任务出来task = taskQue_.front();taskQue_.pop();taskSize_--;//如果依然有剩余任务,继续通知其它的线程执任务if (taskQue_.size() > 0){notEmpty_.notify_all();}//取出一个任务,进行通知,通知可以继续提交生产任务notFull_.notify_all();}//就应该把锁释放掉//当前线程负责执行这个任务if (task != nullptr){//task->run();//执行任务;把任务的返回值setVal方法给到Resulttask->exec();}idleThreadSize_++;//线程执行完任务,空闲线程+1lastTime = std::chrono::high_resolution_clock().now();//更新线程执行完任务的时间}threads_.erase(threadid);// 这个id不是std::this_thread::getid()  是自己生成的,我们自定义的std::cout << "threadid:" << std::this_thread::get_id() << "exit!" << std::endl;exitCond_.notify_all();
}bool ThreadPool::checkRunningState() const
{return isPoolRunning_;
}///   线程方法实现
int Thread::generateId_ = 0;//线程构造
Thread::Thread(ThreadFunc func):func_(func),threadId_(generateId_++)
{}//线程析构
Thread::~Thread(){}//启动线程
void Thread::start()
{//创建一个线程来执行一个线程函数std::thread t(func_, threadId_);//C++11来说 线程对象t  和线程函数func_t.detach();//设置分离线程,线程对象t出作用域会析构,但是线程函数不能结束否则程序会挂掉,所以要将线程分离出去,做到二者互不影响//pthread_detach  pthread_t设置成分离线程//主线程要用pthread_join回收线程,防止孤儿线程的出现}	//获取线程id
int Thread::getId()const
{return threadId_;
}///   Task方法实现
Task::Task():result_(nullptr)
{}void Task::exec()
{result_->setVal(run());//这里发生多态调用
}void Task::setResult(Result* res)
{result_ = res;
}///   Result方法的实现
Result::Result(std::shared_ptr<Task> task, bool isValid):isValid_(isValid),task_(task)
{task_->setResult(this);
}Any Result::get() // 用户调用的
{if (!isValid_){return "";}//task任务如果没有执行完,这里会阻塞用户的线程sem_.wait();//用户调用get时,如果任务在线程池中,还没有被执行完,那么调用get方法的线程就会阻塞住return std::move(any_);//右值引用
}void Result::setVal(Any any)//谁调用的呢??
{//存储task的返回值this->any_ = std::move(any);sem_.post();//已经获取的任务的返回值,增加信号量资源
}

 

我们的资源回收代码如下:

//线程池析构
ThreadPool::~ThreadPool()
{isPoolRunning_ = false;notEmpty_.notify_all();//等待线程池里面所有的线程返回  有两种状态:阻塞 & 正在执行任务中std::unique_lock<std::mutex> lock(taskQueMtx_);exitCond_.wait(lock, [&]()->bool {return threads_.size() == 0; });
}

现在,有的线程没有被回收,线程队列中还有线程,所以就一直阻塞等待了。
线程池的那个线程为什么没有被回收掉?
(时而出现,时而不出现的问题)

我们通过在windows上调试:

我们通过在Linux上进行gdb调试

主要通过gdb attach到正在运行的进程,通过info threads,thread tid,bt等命令查看各个线程的调用堆栈信息,结合项目代码,定位到发生死锁的代码片段,分析死锁问题发生的原因

2.分析问题

原先针对上面的2种情况的处理方法如下:

第3种情况:
有的线程执行完任务,又进入while循环了

在这里有2种情况:
1、pool线程先获取到锁,线程池的线程获取不到锁,阻塞。
此时pool线程看wait条件,size>0,不满足条件,就进入等待wait状态了,并且把互斥锁mutex释放掉。
线程池的线程就获取到锁了,发现任务队列没有任务了,这个任务就在notEmpty条件变量上wait,但是此时pool线程没有办法再对这个条件变量notify了。
发生死锁了!!!

2、线程池里的线程先获取到锁,发生任务队列为空,在条件变量notEmpty上wait了,释放锁,然后pool线程抢到锁,只是看exitCond条件变量的wait条件,看size还是大于0,还是死锁了。

解决方法:pool线程获取到锁后再notify

//线程池析构
ThreadPool::~ThreadPool()
{isPoolRunning_ = false;//等待线程池里面所有的线程返回  有两种状态:阻塞 & 正在执行任务中std::unique_lock<std::mutex> lock(taskQueMtx_);notEmpty_.notify_all();exitCond_.wait(lock, [&]()->bool {return threads_.size() == 0; });
}

 我们在消费者线程进行锁+双重判断:

//定义线程函数   线程池的所有线程从任务队列里面消费任务
void ThreadPool::threadFunc(int threadid)//线程函数返回,相应的线程也就结束了
{auto lastTime = std::chrono::high_resolution_clock().now();//所有任务必须执行完成,线程池才可以回收所有线程资源for (;;){std::shared_ptr<Task> task;{//先获取锁,我们要注意控制锁的范围,取完任务,就释放锁std::unique_lock<std::mutex> lock(taskQueMtx_);std::cout << "tid:" << std::this_thread::get_id()<< "尝试获取任务..." << std::endl;//cached模式下,有可能已经创建了很多的线程,但是空闲时间超过60s,应该把多余的线程//结束回收掉(超过initThreadSize_数量的线程要进行回收)//当前时间 - 上一次线程执行的时间 > 60s//每一秒中返回一次   怎么区分:超时返回?还是有任务待执行返回//锁 + 双重判断while (taskQue_.size() == 0){//线程池要结束,回收线程资源if (!isPoolRunning_){threads_.erase(threadid);//std::this_thread::getid()std::cout << "threadid:" << std::this_thread::get_id() << " exit!"<< std::endl;exitCond_.notify_all();return;//线程函数结束,线程结束}if (poolMode_ == PoolMode::MODE_CACHED){//条件变量,超时返回了if (std::cv_status::timeout ==notEmpty_.wait_for(lock, std::chrono::seconds(1))){auto now = std::chrono::high_resolution_clock().now();auto dur = std::chrono::duration_cast<std::chrono::seconds>(now - lastTime);if (dur.count() >= THREAD_MAX_IDLE_TIME&& curThreadSize_ > initThreadSize_)//任务数量大于空闲线程数量{//开始回收当前线程//记录线程数量的相关变量的值修改//把线程对象从线程列表容器中删除   没有办法 threadFunc《=》thread对象//通过threadid => thread对象 => 删除threads_.erase(threadid);//std::this_thread::getid()curThreadSize_--;idleThreadSize_--;std::cout << "threadid:" << std::this_thread::get_id() << " exit!"<< std::endl;return;}}}else{//等待notEmpty条件notEmpty_.wait(lock);}//if (!isPoolRunning_)//{//	threads_.erase(threadid);//std::this_thread::getid()//	std::cout << "threadid:" << std::this_thread::get_id() << " exit!"//		<< std::endl;//	exitCond_.notify_all();//	return;//结束线程函数,就是结束当前线程了!//}}idleThreadSize_--;std::cout << "tid:" << std::this_thread::get_id()<< "获取任务成功..." << std::endl;//从任务队列种取一个任务出来task = taskQue_.front();taskQue_.pop();taskSize_--;//如果依然有剩余任务,继续通知其它得线程执行任务if (taskQue_.size() > 0){notEmpty_.notify_all();}//取出一个任务,进行通知,通知可以继续提交生产任务notFull_.notify_all();} //就应该把锁释放掉//当前线程负责执行这个任务if (task != nullptr){//task->run();//执行任务;把任务的返回值setVal方法给到Result,基类指针调用派生类对象的同名覆盖方法task->exec();//用户还是使用run方法}idleThreadSize_++;lastTime = std::chrono::high_resolution_clock().now();//更新线程执行完任务的时间}
}

相关文章:

死锁问题分析和解决——资源回收时

1.描述问题 在完成线程池核心功能功能时&#xff0c;没有遇到太大的问题&#xff08;Any,Result,Semfore的设计&#xff09;&#xff0c;在做线程池资源回收时&#xff0c;遇到了死锁的问题 1、在ThreadPool的资源回收&#xff0c;等待线程池所有线程退出时&#xff…...

【Java】效率工具模板的使用

Java系列文章目录 补充内容 Windows通过SSH连接Linux 第一章 Linux基本命令的学习与Linux历史 文章目录 Java系列文章目录一、前言二、学习内容&#xff1a;三、问题描述四、解决方案&#xff1a;4.1 乱码问题4.2 快捷键模板4.3 文件模板 一、前言 提高效率 二、学习内容&am…...

c++指南 -指针和引用

指针和引用 指针的基本概念 指针是存储另一个变量的内存地址的变量。指针变量的声明包括指针类型和星号 (*)。 int* ptr; // ptr 是一个指向 int 类型的指针指针操作 初始化&#xff1a;将指针设置为变量的地址。 int var 10; int* ptr &var; // ptr 现在存储 var 的…...

[CISCN 2023 华北]ez_date

[CISCN 2023 华北]ez_date 点开之后是一串php代码&#xff1a; <?php error_reporting(0); highlight_file(__FILE__); class date{public $a;public $b;public $file;public function __wakeup(){if(is_array($this->a)||is_array($this->b)){die(no array);}if( (…...

前端不同项目使用不同的node版本(Volta管理切换)

前端不同项目使用不同的node版本(Volta管理切换) 使用volta自动切换前端项目的node版本&#xff0c; 每个不同的前端项目&#xff0c;可以使用不同的node版本。Volta这个工具&#xff0c;它允许用户方便地安装、切换和管理不同版本的Node.js&#xff0c;避免了为每个项目手动配…...

Ropdump:针对二进制可执行文件的安全检测工具

关于Ropdump Ropdump是一款针对二进制可执行文件的安全检测工具&#xff0c;该工具基于纯Python开发&#xff0c;是一个命令行工具&#xff0c;旨在帮助广大研究人员检测和分析二进制可执行文件中潜在的ROP小工具、缓冲区溢出漏洞和内存泄漏等安全问题。 功能介绍 1、识别二进…...

Quartz - 定时任务框架集成

参考了若依框架&#xff0c;将quartz定时任务框架集成到自己的项目当中。 目录 一、Quartz概述二、库表创建1.Quartz关键表&#xff08;11张&#xff09;表SQL 2.自定义业务表&#xff08;2张&#xff09;表SQL 三、代码示例1.依赖引入2.类文件1&#xff09;定时任务配置类2&am…...

GoModule

GOPATH 最早的就是GOPATH构建模式&#xff0c; go get下载的包都在path中的src目录下 src目录是源代码存放目录。 package mainimport ("net/http""github.com/gorilla/mux" )func main() {r : mux.NewRouter()r.HandleFunc("/hello", func(w h…...

SQL - 数据库管理

保障数据库安全的用户账户和权限问题&#xff0c;当在工作环境中使用MySQL的时候&#xff0c;我们需要创建其他用户账户&#xff0c;并赋予它们特定权限。创建一个用户 create user wolf127.0.0.1 identified by 1234; create user wolf127.0.0.1 identified by 1234;-- 无 …...

密码学之AES算法

文章目录 1. AES简介1.1 AES算法的历史背景1.2 AES算法的应用领域 2. AES加解密流程图2. AES算法原理2.1 AES加密过程2.2 AES解密过程 1. AES简介 1.1 AES算法的历史背景 AES算法&#xff0c;全称为Advanced Encryption Standard&#xff08;高级加密标准&#xff09;&#x…...

GitHub每日最火火火项目(8.20)

项目名称&#xff1a;goauthentik / authentik 项目介绍&#xff1a;authentik 是一款提供认证功能的工具&#xff0c;它就像是一个强大的粘合剂&#xff0c;能够满足您在认证方面的各种需求。无论是在安全验证、用户身份管理还是访问控制等方面&#xff0c;它都能发挥重要作用…...

(五)Flink Sink 数据输出

经过上面的 Transformation 操作之后,最终形成用户所需要的结果数据集。通常情况下,用户希望将结果数据输出到外部存储介质或者传输到下游的消息中间件中,在 Flink 中,将 DataStream 数据输出到外部系统的过程被定义为 Sink 操作。 目录 (一)基本数据输出 (二)第三方…...

Spring 注入、注解及相关概念补充

一、Spring DI 的理解 DI ( Dependency Inject&#xff0c;中文释义&#xff1a;依赖注入)是对 IOC 概念不同角度的描述&#xff0c;是指应用程序在运行时&#xff0c;每一个 bean 对象都依赖 IOC 容器注入到当前 bean 对象所需要的另一个 bean 对象。&#xff08;例如&#xf…...

【Linux多线程】线程安全的单例模式

文章目录 1. 单例模式 与 设计模式1.1 单例模式1.2 设计模式1.3 饿汉实现模式 与 懒汉实现模式1.4 饿汉模式① 饿汉模式的特点② 饿汉式单例模式的实现③ 饿汉式单例模式的优缺点④ 适用场景 1.5 懒汉模式① 懒汉式单例模式的特点② 懒汉式单例模式的实现③ 懒汉式单例模式的优…...

基于jqury和canvas画板技术五子棋游戏设计与实现(论文+源码)_kaic

摘 要 网络五子棋游戏如今面临着一些新的挑战和机遇。一方面&#xff0c;网络游戏需要考虑到网络延迟和带宽等因素&#xff0c;保证游戏的实时性和稳定性。另一方面&#xff0c;网络游戏需要考虑到游戏的可玩性和趣味性&#xff0c;以吸引更多的玩家参与。本文基于HTML5和Canv…...

指针 (四)

一 . 指针的使用和传值调用 &#xff08;1&#xff09;strlen 的模拟实现 库函数 strlen 的功能是求字符串长度&#xff0c;统计的是字符串中 \0 之前的字符个数&#xff0c;函数原格式如下&#xff1a; 我们的参数 str 接收到一个字符串的起始地址&#xff0c;然后开始统计…...

便利店(超市)管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图详细视频演示技术栈系统测试为什么选择我官方认证玩家&#xff0c;服务很多代码文档&#xff0c;百分百好评&#xff0c;战绩可查&#xff01;&#xff01;入职于互联网大厂&#xff0c;可以交流&#xff0c;共同进步。有保障的售后 代码参考数据库参…...

Excel中的“块”操作

在Excel中&#xff0c;有offset、index、indirect三个对“区域”操作的函数&#xff0c;是较高版本Excel中“块”操作的利器。 (笔记模板由python脚本于2024年08月20日 19:25:21创建&#xff0c;本篇笔记适合喜欢用Excel处理数据的coder翻阅) 【学习的细节是欢悦的历程】 Pytho…...

yolo V8训练 长条状目标

1、说明 目标数据集合中有很多长条状图片&#xff0c;如果直接Resize 会严重拉伸&#xff0c;因此采用把长条图像裁剪成2段&#xff0c;然后将裁剪后的2段图片拼接在一起。 2、代码 2.1 C 代码 &#xff08;部署&#xff0c;模型推理时C &#xff09; #include <stdio.h…...

数据结构与算法 - 设计

1. LRU缓存 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中&#xff0c;则返回关键字的值&#xff0…...

双倍效率:在快马平台中融合chatgpt实现智能代码生成与即时调试

最近在开发过程中&#xff0c;我发现了一个能显著提升效率的工作方式&#xff1a;将ChatGPT的智能生成能力与InsCode(快马)平台的即时调试环境结合起来。这种组合让我在代码编写、问题排查和逻辑优化上都节省了大量时间&#xff0c;今天就来分享一下具体的使用体验。 自然语言…...

NHPZ-10A/10B/10C 型平板式制动检验台全场景实战指南

全工况制动安全闭环&#xff1a;NHPZ-10A/10B/10C 型平板式制动检验台全场景实战指南在机动车安全性能检测体系中&#xff0c;平板式制动检验台是评估车辆制动系统可靠性的核心设备&#xff0c;其检测结果直接决定车辆能否安全上路。传统平板制动检测普遍存在工况模拟失真、数据…...

北海网红美食有哪些

行业现象观察&#xff1a;北海海鲜餐饮的消费图谱在北海&#xff0c;尤其是侨港镇区域&#xff0c;海鲜餐饮呈现出鲜明的“游客本地”双轨特征。晚间时段&#xff0c;从侨港风情街延伸至文化中心一带&#xff0c;用餐高峰时段常出现人流密集、烟火气十足的景象。本地居民多选择…...

Qwen3.5-2B轻量模型评测:端侧推理延迟、功耗、准确率三维平衡点实测

Qwen3.5-2B轻量模型评测&#xff1a;端侧推理延迟、功耗、准确率三维平衡点实测 1. 模型概述 Qwen3.5-2B是通义千问团队推出的轻量化多模态基础模型&#xff0c;属于Qwen3.5系列的小参数版本&#xff08;20亿参数&#xff09;。该模型专为低功耗、低门槛部署场景设计&#xf…...

利用快马平台自动化生成contextmenumanager提升前端开发效率

最近在开发一个后台管理系统时&#xff0c;遇到了一个很常见的需求&#xff1a;需要为表格、图表等元素添加右键菜单功能。这种需求看似简单&#xff0c;但实际开发中却要花费不少时间在重复的配置工作上。经过一番摸索&#xff0c;我发现利用InsCode(快马)平台可以大幅提升这类…...

猫抓:网页资源获取工具的技术革新与实战应用

猫抓&#xff1a;网页资源获取工具的技术革新与实战应用 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 在数字化时代&#xff0c;我们每天浏览大量…...

阅读APP书源完全指南:3种快速导入方法与问题解决方案

阅读APP书源完全指南&#xff1a;3种快速导入方法与问题解决方案 【免费下载链接】Yuedu &#x1f4da;「阅读」自用书源分享 项目地址: https://gitcode.com/gh_mirrors/yu/Yuedu 「阅读」APP书源开源项目为小说爱好者提供了一个强大的解决方案&#xff0c;让您能够在一…...

Qwen3-14B私有AI助手搭建:WebUI可视化界面+本地知识库集成指南

Qwen3-14B私有AI助手搭建&#xff1a;WebUI可视化界面本地知识库集成指南 1. 为什么选择Qwen3-14B私有部署 想象一下&#xff0c;你有一个24小时待命的AI助手&#xff0c;不仅能回答各种专业问题&#xff0c;还能根据你的业务需求进行定制化服务。这就是Qwen3-14B私有部署能为…...

解锁Linux平台微信小程序开发:终极完整环境搭建指南

解锁Linux平台微信小程序开发&#xff1a;终极完整环境搭建指南 【免费下载链接】wechat-web-devtools-linux 适用于微信小程序的微信开发者工具 Linux移植版 项目地址: https://gitcode.com/gh_mirrors/we/wechat-web-devtools-linux 你是否曾为在Linux系统上无法使用微…...

[GDOUCTF 2023]<ez_ze> SSTI 绕过数字与大括号过滤的实战技巧

1. SSTI注入基础与ez_ze题目背景 SSTI&#xff08;Server-Side Template Injection&#xff09;服务器端模板注入是Web安全中常见的漏洞类型&#xff0c;它允许攻击者通过构造恶意模板表达式在服务器端执行任意代码。在CTF竞赛中&#xff0c;这类题目往往通过过滤关键字符来增加…...