linux的C/C++线程池(VS2019开发)

news2024/9/24 1:14:59

文章目录

      • 一、准备工作
      • 二、C语言threadpool实现
      • 三、C++ 11标准实现

代码看视频敲的,非原创

一、准备工作

创建项目
在这里插入图片描述
连接linux虚拟机
在这里插入图片描述

在这里插入图片描述
启动测试:VS2019运行Linux程序报错:无法启动gdb。系统中缺少gdb。sudo yum install -y gdb

线程池的组成主要分为3个部分,这三部分配合工作就可以得到一个完整的线程池:

  1. 任务队列,存储需要处理的任务,由工作的线程来处理这些任务
    通过线程池提供的API函数,将一个待处理的任务添加到任务队列,或者从任务队列中删除
    已处理的任务会被从任务队列中删除
    线程池的使用者,也就是调用线程池函数往任务队列中添加任务的线程就是生产者线程
  2. 工作的线程(任务队列任务的消费者) ,N个
    线程池中维护了一定数量的工作线程, 他们的作用是是不停的读任务队列, 从里边取出任务并处理
    工作的线程相当于是任务队列的消费者角色,
    如果任务队列为空, 工作的线程将会被阻塞 (使用条件变量/信号量阻塞)
    如果阻塞之后有了新的任务, 由生产者将阻塞解除, 工作线程开始工作
  3. 管理者线程(不处理任务队列中的任务),1个
    它的任务是周期性的对任务队列中的任务数量以及处于忙状态的工作线程个数进行检测
    当任务过多的时候, 可以适当的创建一些新的工作线程
    当任务过少的时候, 可以适当的销毁一些工作的线程

二、C语言threadpool实现

在这里插入图片描述

threadpool.h

#pragma once
#ifndef _THREADPOOL_H
#define _THREADPOOL_H
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>




// 任务结构体
typedef struct Task
{
	void (*function)(void* arg);
	void* arg;
}Task;


// 线程池结构体
typedef struct ThreadPool
{
	// 任务队列
	Task* taskQ;
	int queueCapacity;  // 容量
	int queueSize;      // 当前任务个数
	int queueFront;     // 队头 -> 取数据
	int queueRear;      // 队尾 -> 放数据

	pthread_t managerID;    // 管理者线程ID
	pthread_t* threadIDs;   // 工作的线程ID
	int minNum;             // 最小线程数量
	int maxNum;             // 最大线程数量
	int busyNum;            // 忙的线程的个数
	int liveNum;            // 存活的线程的个数
	int exitNum;            // 要销毁的线程个数
	pthread_mutex_t mutexPool;  // 锁整个的线程池
	pthread_mutex_t mutexBusy;  // 锁busyNum变量
	pthread_cond_t notFull;     // 任务队列是不是满了
	pthread_cond_t notEmpty;    // 任务队列是不是空了

	int shutdown;           // 是不是要销毁线程池, 销毁为1, 不销毁为0
}ThreadPool;



// 创建线程池并初始化
ThreadPool* threadPoolCreate(int min, int max, int queueSize);

// 销毁线程池
int threadPoolDestroy(ThreadPool* pool);

// 给线程池添加任务
void threadPoolAdd(ThreadPool* pool, void(*func)(void*), void* arg);

// 获取线程池中工作的线程的个数
int threadPoolBusyNum(ThreadPool* pool);

// 获取线程池中活着的线程的个数
int threadPoolAliveNum(ThreadPool* pool);

//
// 工作的线程(消费者线程)任务函数
void* worker(void* arg);

// 管理者线程任务函数
void* manager(void* arg);

// 单个线程退出
void threadExit(ThreadPool* pool);



#endif  // _THREADPOOL_H

threadpool.c

#include "threadpool.h"

const int NUMBER = 2;

ThreadPool* threadPoolCreate(int min, int max, int queueSize)
{
	ThreadPool* pool = (ThreadPool*)malloc(sizeof(ThreadPool));
	do
	{
		if (pool == NULL)
		{
			printf("malloc threadpool failed...\n");
			break;
		}

		pool->threadIDs = (pthread_t*)malloc(sizeof(pthread_t) * max);
		if (pool->threadIDs == NULL)
		{
			printf("malloc threadIDs failed...\n");
			break;
		}
		memset(pool->threadIDs, 0, sizeof(pthread_t) * max);
		pool->minNum = min;
		pool->maxNum = max;
		pool->busyNum = 0;
		pool->liveNum = min;    // 和最小个数相等
		pool->exitNum = 0;

		if (pthread_mutex_init(&pool->mutexPool, NULL) != 0 ||
			pthread_mutex_init(&pool->mutexBusy, NULL) != 0 ||
			pthread_cond_init(&pool->notEmpty, NULL) != 0 ||
			pthread_cond_init(&pool->notFull, NULL) != 0)
		{
			printf("mutex or condition init fail...\n");
			break;
		}

		// 任务队列
		pool->taskQ = (Task*)malloc(sizeof(Task) * queueSize);
		pool->queueCapacity = queueSize;
		pool->queueSize = 0;
		pool->queueFront = 0;
		pool->queueRear = 0;
		pool->shutdown = 0;

		// 创建线程
		pthread_create(&pool->managerID, NULL, manager, pool);
		for (size_t i = 0; i < min; i++)
		{
			pthread_create(&pool->threadIDs[i], NULL, worker, pool);
		}
		return pool;
	} while (0);

	//释放资源
	if (pool && pool->threadIDs) free(pool->threadIDs);
	if (pool && pool->taskQ) free(pool->taskQ);
	if (pool) free(pool);
	return NULL;
}


int threadPoolDestroy(ThreadPool* pool)
{
	if (pool == NULL)
	{
		return -1;
	}

	//关闭线程池
	pool->shutdown = 1;

	//阻塞回收管理者线程
	pthread_join(pool->managerID, NULL);

	//唤醒阻塞消费者线程
	for (size_t i = 0; i < pool->liveNum; i++)
	{
		pthread_cond_signal(&pool->notEmpty);
	}

	//释放申请的堆内存
	if (pool->taskQ) free(pool->taskQ);
	if (pool->threadIDs) free(pool->threadIDs);


	//释放互斥锁
	pthread_mutex_destroy(&pool->mutexBusy);
	pthread_mutex_destroy(&pool->mutexPool);
	pthread_mutex_destroy(&pool->notEmpty);
	pthread_mutex_destroy(&pool->notFull);

	free(pool);
	return 0;
}


void threadPoolAdd(ThreadPool* pool, void(*func)(void*), void* arg)
{
	pthread_mutex_lock(&pool->mutexPool);
	while (pool->queueSize == pool->queueCapacity && !pool->shutdown)
	{
		//阻塞生产者线程
		pthread_cond_wait(&pool->notFull, &pool->mutexPool);

	}
	if (pool->shutdown)
	{
		pthread_mutex_unlock(&pool->mutexPool);
		return;
	}

	//添加任务到队尾
	pool->taskQ[pool->queueRear].function = func;
	pool->taskQ[pool->queueRear].arg = arg;
	pool->queueRear = (pool->queueRear + 1) % pool->queueCapacity;
	pool->queueSize++;

	pthread_cond_signal(&pool->notEmpty);

	pthread_mutex_unlock(&pool->mutexPool);
}


int threadPoolBusyNum(ThreadPool* pool)
{
	pthread_mutex_lock(&pool->mutexBusy);
	int busyNum = pool->busyNum;
	pthread_mutex_unlock(&pool->mutexBusy);
	return busyNum;
}


int threadPoolAliveNum(ThreadPool* pool)
{
	pthread_mutex_lock(&pool->mutexPool);
	int Alive = pool->liveNum;
	pthread_mutex_unlock(&pool->mutexPool);
	return Alive;
}


void* worker(void* arg)
{
	ThreadPool* pool = (ThreadPool*)arg;
	while (1)
	{
		pthread_mutex_lock(&pool->mutexPool);
		//当前任务队列是否为空
		while (pool->queueSize == 0 && !pool->shutdown)
		{
			//阻塞工作线程
			pthread_cond_wait(&pool->notEmpty, &pool->mutexPool);

			//判断是不是要销毁线程
			if (pool->exitNum > 0)
			{
				pool->exitNum--;
				if (pool->liveNum > pool->minNum)
				{
					pool->liveNum--;
					pthread_mutex_unlock(&pool->mutexPool);
					threadExit(pool);
				}
			}
		}

		//判断当前线程是否被关闭了
		if (pool->shutdown)
		{
			pthread_mutex_unlock(&pool->mutexPool);
			threadExit(pool);
		}

		//从任务队列中取出一个任务
		Task task;
		task.function = pool->taskQ[pool->queueFront].function;
		task.arg = pool->taskQ[pool->queueFront].arg;
		//移动头部指针指向下一个节点
		pool->queueFront = (pool->queueFront + 1) % pool->queueCapacity;
		pool->queueSize--;

		//唤醒生产者
		pthread_cond_signal(&pool->notFull);
		pthread_mutex_unlock(&pool->mutexPool);


		printf("thread %ld start working...\n", pthread_self());
		pthread_mutex_lock(&pool->mutexBusy);
		pool->busyNum++;
		pthread_mutex_unlock(&pool->mutexBusy);
		task.function(task.arg);
		free(task.arg);
		task.arg = NULL;

		printf("thread %ld end worked...\n", pthread_self());
		pthread_mutex_lock(&pool->mutexBusy);
		pool->busyNum--;
		pthread_mutex_unlock(&pool->mutexBusy);
	}
	return NULL;
}


void* manager(void* arg)
{
	ThreadPool* pool = (ThreadPool*)arg;
	while (!pool->shutdown)
	{
		//每3s检测一次
		sleep(3);

		//取出线程池中任务的数量和当前线程的个数
		pthread_mutex_lock(&pool->mutexPool);
		int queueSize = pool->queueSize;
		int liveNum = pool->liveNum;
		pthread_mutex_unlock(&pool->mutexPool);

		//取出线程池中忙碌线程的个数
		pthread_mutex_lock(&pool->mutexBusy);
		int busyNum = pool->busyNum;
		pthread_mutex_unlock(&pool->mutexBusy);
		
		// 添加线程(当前任务个数>存活线程个数 && 存活线程个数< 最大线程数)
		if (queueSize > liveNum && liveNum < pool->maxNum)
		{
			pthread_mutex_lock(&pool->mutexPool);
			int counter = 0;
			for (size_t i = 0; i < pool->maxNum 
				&& counter < NUMBER
				&& pool->liveNum < pool->maxNum; i++) 
			{
				if (pool->threadIDs[i] == 0)
				{
					pthread_create(&pool->threadIDs[i], NULL, worker, pool);
					counter++;
					pool->liveNum++;
				}
			}
			pthread_mutex_unlock(&pool->mutexPool);
		}
		
		// 销毁线程(忙的线程*2 < 存活的线程数 && 存活的线程数 > 最小的线程数)
		if (busyNum * 2 < liveNum && liveNum > pool->minNum) 
		{
			pthread_mutex_lock(&pool->mutexPool);
			pool->exitNum = NUMBER;
			pthread_mutex_unlock(&pool->mutexPool);

			//让工作线程自杀
			for (size_t i = 0; i < NUMBER; i++) 
			{
				pthread_cond_signal(&pool->notEmpty);
			}
		}
	}
	return NULL;
}


void threadExit(ThreadPool* pool)
{
	pthread_t tid = pthread_self();
	for (size_t i = 0; i < pool->maxNum; i++)
	{
		if (pool->threadIDs[i] == tid)
		{
			pool->threadIDs[i] = 0;
			printf("threadExit  %ld exiting...", tid);
			break;
		}
	}
	pthread_exit(NULL);
}

main.c

#include "threadpool.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>



void taskFunc(void* arg)
{
	int num = *(int*)arg;
	printf("thread %ld is working, number = %d\n", pthread_self(), num);
	sleep(1);
}

int main(int argc, char *argv[])
{
    printf("%s 向你问好!\n", "windows to linux");


	// 创建线程池
	ThreadPool* pool = threadPoolCreate(4, 10, 100);
	for (int i = 0; i < 100; ++i)
	{
		int* num = (int*)malloc(sizeof(int));
		*num = i + 100;
		threadPoolAdd(pool, taskFunc, num);
	}

	sleep(10);
	threadPoolDestroy(pool);
	return 0;
}

在这里插入图片描述
https://gitee.com/lc123/thread-pool

三、C++ 11标准实现

三个文件:TaskQueue.hpp, ThreadPool.hpp, main.cpp
TaskQueue.hpp

#pragma once
#include <iostream>
#include <queue>
#include <mutex>
#include <functional>
using namespace std;



typedef function<void(void*)> callback;


template<class T>
class Task {
public:
	Task() {
		function = nullptr;
		arg = nullptr;
	}
	Task(callback f, void *arg) {
		function = f;
		this->arg = (T *)arg;
	}
	callback function;
	T *arg;
};


template<class T>
class TaskQueue {
public:

	TaskQueue() {};
	~TaskQueue() {};

	void addTask(const Task<T>& task) 
	{
		lock_guard<mutex> lock(mtx);
		taskQueue.push(task);
	};

	Task<T> takeTask()
	{
		lock_guard<mutex> lock(mtx);
		Task<T> task;
		if (!taskQueue.empty()) {
			task = taskQueue.front();
			taskQueue.pop();
		}
		return task;
	};

	inline unsigned int taskNumber() 
	{
		return taskQueue.size();
	}

private:
	queue<Task<T>> taskQueue;
	mutex mtx;
};

ThreadPool.hpp

#pragma once
#include<iostream>
#include<queue>
#include<mutex>
#include<thread>
#include<string.h>
#include<atomic>
#include<vector>
#include<condition_variable>
#include"TaskQueue.hpp"
#include<signal.h>

using namespace std;


//线程池结构体
template<class T>
class ThreadPool {
public:
	ThreadPool();
	explicit ThreadPool(unsigned int min, unsigned int max);
	~ThreadPool();

	void addTask(const Task<T> &task);
	unsigned int getBusyNum(); //忙的线程个数
	unsigned int getAliveNum(); //活的线程个数
private:
	void threadExit();//线程退出
	static void * worker(void *arg);//工作线程调用的任务函数
	static void * manager(void *arg);//管理者线程调用的任务函数

	const static unsigned int NUMBER = 2;//一次加入的线程数

	//任务队列
	TaskQueue<T> *taskQueue;

	thread *managerThread; //管理者线程

	vector<thread> workGroup; //工作线程组

	unsigned int minNum; //最小线程数
	unsigned int maxNum; //最大线程数

	atomic<unsigned int> busyNum; //忙的线程的个数
	atomic<unsigned int> liveNum; //存活的线程的个数
	atomic<unsigned int> exitNum; //要销毁的线程个数

	mutex mutexPool; //锁整个线程池
	condition_variable notEmpty; //任务队列是不是空了 利用条件变量通知

	bool isShutdown; //线程池销毁 true 
};

template<class T>
ThreadPool<T>::ThreadPool() {}

template<class T>
ThreadPool<T>::ThreadPool(unsigned int min, unsigned int max) {
	taskQueue = new TaskQueue<T>;
	if (nullptr == taskQueue) {
		cout << "new taskQueue fail..." << endl;
		return;
	}
	workGroup.reserve(max);   //分配最大容量

	minNum = min;
	maxNum = max;
	busyNum = 0;
	liveNum = min;
	exitNum = 0;

	isShutdown = false;
	
	//管理者线程
	managerThread = new thread(manager, this);
	if (nullptr == managerThread) {
		cout << "new managerThread fail..." << endl;
		return;
	}
	//工作线程
	for (unsigned int i = 0; i < min; ++i) {
		workGroup.push_back(thread(worker, this));
	}
}

template<class T>
ThreadPool<T>::~ThreadPool() {
	
	isShutdown = true;
	//唤醒工作线程 主动关闭
	if (liveNum > 0) {
		notEmpty.notify_all();
	}
	if (taskQueue) {
		delete taskQueue;
	}
	if (managerThread != nullptr) {
		//设置managerThread为守护线程时
		//c++运行库可以保证managerThread相关资源回收
		if (managerThread->joinable()) {
			managerThread->join();
		}
		delete managerThread;
	}
	if (!workGroup.empty()) {
		threadExit();
	}
	cout << "ThreadPool end!" << endl;
}



template<class T>
void * ThreadPool<T>::worker(void *arg) {
	cout << this_thread::get_id() << " worker thread starting..." << endl;

	ThreadPool *pool = static_cast<ThreadPool *>(arg);
	while (true) {
		unique_lock<mutex> poolLock(pool->mutexPool);
		while (0 == pool->taskQueue->taskNumber() && !pool->isShutdown) {
			cout << this_thread::get_id() << " waiting task..." << endl;
			pool->notEmpty.wait(poolLock);//阻塞等待任务,唤醒后自动上锁
			//判断是否销毁线程
			if (pool->exitNum > 0 && pool->liveNum > pool->minNum) {
				pool->exitNum--;
				pool->liveNum--;
				poolLock.unlock();
				cout << this_thread::get_id() << " thread end work" << endl;
				return NULL;
			}
		}
		if (pool->isShutdown) {
			poolLock.unlock();
			cout << this_thread::get_id() << " thread end work" << endl;
			return NULL;
		}
		//取出一个任务
		Task<T> task = pool->taskQueue->takeTask();
		poolLock.unlock();

		pool->busyNum++;
		task.function(task.arg);//执行函数
		delete task.arg;
		pool->busyNum--;
	}
}

template<class T>
void * ThreadPool<T>::manager(void *arg) {
	cout << this_thread::get_id() << " manager thread starting..." << endl;
	ThreadPool *pool = static_cast<ThreadPool *>(arg);

	int checkInterval = 3;
	while (!pool->isShutdown) {
		this_thread::sleep_for(chrono::seconds(checkInterval));//每3秒检查
		unique_lock<mutex> poolLock(pool->mutexPool);
		unsigned int queueSize = pool->taskQueue->taskNumber();
		//唤醒等待任务的线程
		unsigned int sleepThreadNum = pool->liveNum - pool->busyNum;
		if (pool->busyNum < queueSize) {
			while (sleepThreadNum > 0) {
				pool->notEmpty.notify_one();
				sleepThreadNum--;
			}
		}
		//添加线程
		if (queueSize > pool->liveNum && pool->liveNum < pool->maxNum) {
			for (unsigned int counter = 0; counter < NUMBER
				&& pool->liveNum < pool->maxNum; ++counter) {
				pool->workGroup.push_back(thread(worker, pool));
				pool->liveNum++;
			}
		}
		//检查时间自适应
		//还需完善
		if ((queueSize * 2 < pool->liveNum || queueSize > 2 * pool->liveNum)
			&& checkInterval > 1) {
			checkInterval--;
		}
		poolLock.unlock();
		//销毁线程
		if (pool->busyNum * 2 < pool->liveNum && pool->liveNum > pool->minNum) {
			pool->exitNum = NUMBER;
			for (int i = 0; i < NUMBER; ++i) {
				pool->notEmpty.notify_one();
			}
		}
		//代表有些线程以经结束了工作,需要从容器中删除,回收资源
		if (pool->workGroup.size() > pool->liveNum) {
			pool->threadExit();
		}
	}
	return NULL;
}

template<class T>
void ThreadPool<T>::threadExit() {
	//清空容器里结束的线程
	//thread::id tid = this_thread::get_id();
	auto it = workGroup.begin();
	while (it != workGroup.end()) {
		auto correntTid = (*it).native_handle();//获取线程pthread_id
		int kill_rc = pthread_kill(correntTid, 0);//发送信号0,探测线程是否存活
		if (kill_rc == ESRCH) {
			//the specified thread did not exists or already quit
			if ((*it).joinable()) {
				(*it).join();//等待线程结束 清理线程存储
			}
			it = workGroup.erase(it);
			cout << "thread " << correntTid << " exit from group" << endl;
		}
		else {
			++it;
		}
	}
}

template<class T>
void ThreadPool<T>::addTask(const Task<T> &task) {
	taskQueue->addTask(task);
}

template<class T>
unsigned int ThreadPool<T>::getBusyNum() {
	return this->busyNum;
}

template<class T>
unsigned int ThreadPool<T>::getAliveNum() {
	return this->liveNum;
}

main.cpp

#include <iostream>
#include <future>
#include <thread>
#include "TaskQueue.hpp"
#include "ThreadPool.hpp"
using namespace std;


void task(void *arg) {
	int num = *(int *)arg;
	cout << "corrent id is: " << this_thread::get_id() << " :" << num << endl;
	this_thread::sleep_for(chrono::seconds(1));
}


void test1() {
	cout << "start threadPool test" << endl;
	ThreadPool<int> pool(2, 10);
	for (int i = 0; i < 100; ++i) {
		int *num = new int(i + 100);
		pool.addTask(Task<int>(task, num));
	}
	this_thread::sleep_for(chrono::seconds(15));
	for (int i = 0; i < 20; ++i) {
		int* num = new int(i + 200);
		pool.addTask(Task<int>(task, num));
	}
	this_thread::sleep_for(chrono::seconds(20));
	cout << "end threadPool test" << endl;
}

int main() {
	test1();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/806693.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Maven】Maven配置国内镜像

文章目录 1. 配置maven的settings.xml文件1.1. 先把镜像mirror配置好1.2. 再把仓库配置好 2. 在idea中引用3. 参考资料 网上配置maven国内镜像的文章很多&#xff0c;为什么选择我&#xff0c;原因是&#xff1a;一次配置得永生、仓库覆盖广、仓库覆盖全面、作者自用的配置。 1…

matlab使用教程(5)—矩阵定义和基本运算

本博客介绍如何在 MATLAB 中创建矩阵和执行基本矩阵计算。 MATLAB 环境使用矩阵来表示包含以二维网格排列的实数或复数的变量。更广泛而言&#xff0c;数组为向量、矩阵或更高维度的数值网格。MATLAB 中的所有数组都是矩形&#xff0c;在这种意义上沿任何维度的分量向量的长度…

启动Anaconda卡在loading applications的解决办法

启动Anaconda卡在 loading applications的解决办法 问题解决方法 问题 系统环境&#xff1a;macOS BigSur v11.2.2 启动anaconda后&#xff0c;卡在 loading applications界面。 解决方法 在anaconda安装目录下找到conda_api.py文件&#xff0c;将 data yaml.load(f)修改为…

【沐风老师】3dMax子样条线编辑插件SubSpline使用方法详解

3dMax子样条线编辑插件SubSpline&#xff0c;是3dMax中样条曲线形状的高级子对象选择器和材质ID编辑器。 只需一个简单的切换按钮&#xff0c;即可在屏幕上轻松显示所有选定形状的顶点编号和材质ID。 利用箭头工具选择样条曲线子对象&#xff0c;以补充和扩展3dsMax的标准工具…

推荐功能强大的活码管理平台(支持淘宝客和分享卡片)

功能强大的活码管理平台源码-支持淘宝客和分享卡片等功能 演示地址&#xff1a;runruncode.com/code/19494.html 首页 查看群活码、客服码、渠道码当天总访问量查看成员账号个数查看群活码、客服码、渠道码当天各时段访问量 群活码 创建、编辑、删除、分享群活码查看群活码…

使用MyBatis(2)

目录 一、定义接口、实体类、创建XML文件实现接口&#xff09; 二、MyBatis的增删改查 &#x1f345;1、MyBatis传递参数查询 &#x1f388;写法一 &#x1f388;写法二 &#x1f388;两种方式的区别 &#x1f345;2、删除操作 &#x1f345;3、根据id修改用户名 &#x…

推荐带500创作模型的付费创作V2.1.0独立版系统源码

ChatGPT 付费创作系统 V2.1.0 提供最新的对应版本小程序端&#xff0c;上一版本增加了 PC 端绘画功能&#xff0c; 绘画功能采用其他绘画接口 – 意间 AI&#xff0c;本版新增了百度文心一言接口。 后台一些小细节的优化及一些小 BUG 的处理&#xff0c;前端进行了些小细节优…

fasta序列转为数字0和1-python

原始文件&#xff1a; 目标文件&#xff1a; linux版本 #name:lin_convert_fasta_to_01.py #! /usr/bin/env python #usage: python hash-always.py -l 1.list -f 2.txt > out.txt import argparse parser argparse.ArgumentParser(description"Advanced screeni…

训练自己的行文本检测EAST模型

训练自己的行文本检测EAST模型 训练数据格式 训练数据格式

Mybatis初识(一)

一.Mybatis是什么 MyBatis 是一款优秀的持久层框架&#xff0c;它支持自定义SQL、存储过程以及高级映射。MyBatis 去除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的XML或注解来配置,和映射原始类型、接口和Java POJO (Plain Old Java Objects…

UDS诊断协议

UDS本质上是一系列服务的集合&#xff0c;包含6大类&#xff0c;共26种。每种服务都有独立的ID&#xff0c;即SID。 请求 SID(1Byte) 参数 SID(1Byte) Sub-function(1Byte) 参数 SID DID(2Bytes) 响应 肯定响应 SID0x40(1Byte) Sub-function(根据请求是否存在) 参数…

【C++进阶】继承

⭐博客主页&#xff1a;️CS semi主页 ⭐欢迎关注&#xff1a;点赞收藏留言 ⭐系列专栏&#xff1a;C进阶 ⭐代码仓库&#xff1a;C进阶 家人们更新不易&#xff0c;你们的点赞和关注对我而言十分重要&#xff0c;友友们麻烦多多点赞&#xff0b;关注&#xff0c;你们的支持是我…

收集springboot2.6和2.7新功能新特性,以便后续项目中使用

更多功能看演示系统 gitee源代码地址 后端代码: https://gitee.com/nbacheng/nbcio-boot 前端代码:https://gitee.com/nbacheng/nbcio-vue.git 在线演示(包括H5) : http://122.227.135.243:9888 因为nbcio-boot已经升级到2.7,所以先收集SpringBoot2.6和2.7的新功能,…

数据结构:链表的一些经典的OJ题目

文章目录 写在前面链表OJ调试技巧移除链表元素反转链表链表的中间节点链表中倒数第K个节点链表分割问题 写在前面 本篇为本人学习链表的过程中遇到的典型OJ题&#xff0c;于是整理出来分享思路和便于后续重新学习&#xff0c;每个标题均可跳转至对应习题&#xff0c;大多为Lee…

Autosar通信实战系列02-CAN报文发送周期测试脚本开发及周期不准优化

本文框架 前言1. CAN发送报文的周期测试脚本开发2. 发送报文周期不准的可能原因及优化策略2.1 发送报文的控制逻辑2.2 送报文周期不准的可能原因及优化策略前言 在本系列笔者将结合工作中对通信实战部分的应用经验进一步介绍常用,包括但不限于通信各模块的开发教程,代码逻辑…

nvidia显卡设置 让显卡发挥最大的性能

1、打开官网https://www.nvidia.cn/geforce/drivers/ 查看电脑系统位数和显卡(GPU)的版本 产品系列&#xff1a;Notebooks表示笔记本 2、点击【搜索】-【下载】(game表示游戏驱动)-【下载】 3、双击运行exe文件 4、使用邮箱注册账号时注意要设置正常的年龄 PS设置 1、设置PS使用…

2023 7-29

题目1 删除排序链表重复元素 思路和代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* ListNode(int x, ListNode *n…

某ERP系统信息泄露登录后台

漏洞描述 某ERP前台泄露了某api接口,恶意攻击者可通过调用该接口,对用户的账号和密码进行非授权访问,在获取到账号和密码后,恶意攻击者可接管后台。 免责声明 技术文章仅供参考,任何个人和组织使用网络应当遵守宪法法律,遵守公共秩序,尊重社会公德,不得利用网络从事…

Unity 工具之 NuGetForUnity 包管理器,方便在 Unity 中的进行包管理的简单使用

Unity 工具之 NuGetForUnity 包管理器&#xff0c;方便在 Unity 中的进行包管理的简单使用 目录 Unity 工具之 NuGetForUnity 包管理器&#xff0c;方便在 Unity 中的进行包管理的简单使用 一、简单介绍 二、NuGetForUnity 的下载导入 Unity 三、NuGetForUnity 在 Unity 的…

文件夹加密软件哪个好?文件夹加密软件排行榜

想要加密文件夹&#xff0c;使用专业的文件夹加密软件可以安全有效地加密电脑文件夹。那么&#xff0c;文件夹加密软件哪个好&#xff1f;文件夹加密软件排行榜了解一下。 TOP 1&#xff1a;文件夹加密超级大师 软件亮点&#xff1a;极致安全&#xff0c;5种文件夹加密类型 文…