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

手写线程池 - C++版 - 笔记总结

1.线程池原理

创建一个线程,实现很方便。
缺点:若并发的线程数量很多,并且每个线程都是执行一个时间较短的任务就结束了。
      由于频繁的创建线程和销毁线程需要时间,这样的频繁创建线程会大大降低
      系统的效率。

2.思考 :怎么样实现线程复用,让线程执行完一个任务后不被销毁,还可以继续执行其他的任务?

答:线程池

3.思考 :什么是线程池?

可以点击看爱编程的大丙写的这边文章!写的很好

 4.线程池的组成:任务队列、工作的线程、管理者线程

(1)任务队列
听到任务队列之后,咱们在脑海里应该闪现出另外一个模型,就是生产者和消费者模型。因为只要
有任务队列,就离不开生产者和消费者模型。为什么要有任务队列呢?就是因为他有生产者要把生
产出来的数据或者商品进行存储,存储起来之后让对应的某些消费者去消费。在我们写程序的时候,
就是有一些生产者线程还是负责往这个任务队列里边放任务,然后有一些消费者线程负责把任务从
任务队列中取出去,并且把它处理掉。在线程池里边有任务队列,也就意味着这个线程池它是生产者
和消费者模型里边的一部分。哪一个部分呢?这个线程池主要是负责维护一个任务队列,然后再维护
若干个消费者线程。它维护的线程都是消费者线程。
那么生产者是谁呢?谁使用任务队列,谁就是生产者。那么这个生产者它把任务放到任务队列里边。
怎么放呢?肯定是通过线程池提供的api接口,把这个任务放到任务队列,,放进来之后,这个消费
者线程通过一个循环,不停地从这个任务队列里边去取任务,
假设说咱们这个任务队列为空了,消费者线程就需要阻塞了。可以使用条件变量把它阻塞掉就行了。
如果让消费者线程去阻塞,它就放弃了CPU时间片了,这个对系统的资源消耗是一点都没有的。
对于生产者来说,假设这个任务队列已经满了,需要阻塞生产者。当这个消费者消费了这个产品之后,
任务队列就不再满了,那么任务队列不再满之后,咱们就让消费者把这个生产者唤醒,让他们继续生
产。这个任务队列是两个角色,而不是三个角色,这一点要搞明白的!!!(2)工作的线程
这个任务队列里边的任务都是函数地址,工作的线程在处理这个任务的时候,它就基于这个函数地址
对那个函数进行调用。也就说这个任务队列里边的任务都是回调函数。
什么时候去回调呢?
就是当线程池里边的这个消费者线程把它取出去之后,它就被调用了。如果说没有被取出来,他就不
被调用了。(3)管理者线程
管理者线程的职责非常单一,就是不停的去检测当前任务队列里边任务的个数,还有当前工作的线程
的线程数,然后针对于它们的数量进行一个运算。看一看现在需要添加线程还是销毁线程。干这个事
的时候,可以给它设置一个频率,比如说你可以让他五秒钟去做一次或者十秒钟去做一次。sleep管理
者是非常轻松的

(一)任务类Task的定义

#pragma once
#include <queue>
#include <pthread.h>using callback = void (*)(void *);// 任务结构体
template <typename T>
struct Task
{Task<T>(){function = nullptr;arg = nullptr;}Task<T>(callback f, void *arg){this->arg = (T *)arg;function = f;}callback function;T *arg;
};

(二)任务队列TaskQueue的定义

template <typename T>
class TaskQueue
{
public:TaskQueue();~TaskQueue();// 添加任务void addTask(Task<T> task);void addTask(callback f, void *arg);// 取出一个任务Task<T> takeTask();// 获取当前任务的个数inline size_t taskNumber(){return m_taskQ.size();}private:std::queue<Task<T> > m_taskQ;pthread_mutex_t m_mutex;
};

(三)线程池 ThreadPool 类的声明

#pragma once
#include "TaskQueue.h"
template <typename T>
class ThreadPool
{
public:// 创建线程池并初始化ThreadPool(int min, int max);// 销毁线程池~ThreadPool();// 给线程池添加任务void addTask(Task<T> task);// 获取线程池中工作的线程的个数int getBusyNum();// 获取线程池中活着的线程个数int getAliveNum();private:// 工作的线程是(消费者线程)任务函数static void *worker(void *arg);// 管理者线程任务函数static void *manager(void *arg);// 单个线程退出void threadExit();private:// 任务队列TaskQueue<T> *taskQ;pthread_t managerID;       // 管理者线程IDpthread_t *threadIDs;      // 工作的线程IDint minNum;                // 最小线程数量int maxNum;                // 最大线程数量int busyNum;               // 忙的线程的个数int liveNum;               // 存活的线程的个数int exitNum;               // 要销毁的线程个数pthread_mutex_t mutexPool; // 锁整个的线程池pthread_cond_t notEmpty;   // 任务队列是不是空了bool shutdown = false; // 是不是要销毁线程池static const int NUMBER = 2;
};

(四)线程池 构造函数

template <typename T>
ThreadPool<T>::ThreadPool(int min, int max)
{do{// 实例化任务队列taskQ = new TaskQueue<T>;if (taskQ == nullptr){std::cout << "new taskQ fail..." << std::endl;break;}// 根据线程的最大上限给线程数组分配内存threadIDs = new pthread_t[max];if (threadIDs == nullptr){std::cout << "new threadIDs fail..." << std::endl;break;}// 初始化memset(threadIDs, 0, sizeof(pthread_t) * max);minNum = min;maxNum = max;busyNum = 0;liveNum = min; // 和最小个数相等exitNum = 0;// 初始化互斥锁,条件变量if (pthread_mutex_init(&mutexPool, NULL) != 0 ||pthread_cond_init(&notEmpty, NULL) != 0){std::cout << "mutex or condition init fail...\n"<< std::endl;break;}shutdown = false;/// 创建线程 ////"void *(ThreadPool::*)(void *arg)" 类型的实参与 "void *(*)(void *)" 类型的形参不兼容// 类的静态成员或者类的外部的非类普通函数,只要定义出来之后就有地址// 如果是类的成员函数,并且不是静止的,这个函数定义出来是没有地址的// 什么时候有地址呢?我们必须要给这个类进行实例化,创建类的对象,这个// 函数才有地址// this指针指向当前被实例化的对象,如果要是在外边new出来一个ThreadPool对象,// 那么这个this指针就指向该实例化对象// 为什么要把this传给manager呢?// 因为manager是一个静态方法,静态方法它只能访问类里边的静态成员变量,// 它不能访问类的非静态成员变量。// 因此如果我们想要访问这些非静态成员变量,就必须要给这个静态方法传进去一个// 实例化对象,通过传进去的这个实例化对象来访问里边的非静态成员函数和变量// 创建管理者线程,1个pthread_create(&managerID, NULL, manager, this);// 根据最小线程个数,创建线程for (int i = 0; i < min; i++){pthread_create(&threadIDs[i], NULL, worker, this);}return;} while (0);// 释放资源if (threadIDs)delete[] threadIDs;if (taskQ)delete taskQ;
}

(五)工作的线程的任务函数

// 工作线程任务函数
template <typename T>
void *ThreadPool<T>::worker(void *arg)
{ThreadPool *pool = static_cast<ThreadPool *>(arg);// 一直不停的工作while (true){// 访问任务队列(共享资源)加锁pthread_mutex_lock(&pool->mutexPool);// 当前任务队列是否为空,如果为空工作线程阻塞while (pool->taskQ->taskNumber() == 0 && !pool->shutdown){//printf("thread %ld waiting...\n",pthread_self());// 阻塞工作线程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);pool->threadExit();}}}// 判断线程池是否被关闭了if (pool->shutdown){pthread_mutex_unlock(&pool->mutexPool);pool->threadExit();}// 从任务队列中取出一个任务Task<T> task = pool->taskQ->takeTask();// 工作的线程+1pool->busyNum++;// 线程池解锁pthread_mutex_unlock(&pool->mutexPool);// 执行任务printf("thread %ld start working...\n", pthread_self());// std::cout<<"thread "<<to_string(pthread_self()) <<" start working..."<<std::endl;task.function(task.arg);delete task.arg;task.arg = nullptr;// 任务处理结束printf("thread %ld end working...\n", pthread_self());// std::cout<<"thread "<<std::string to_string(pthread_self()) <<" end working..."<<std::endl;pthread_mutex_lock(&pool->mutexPool);pool->busyNum--;pthread_mutex_unlock(&pool->mutexPool);}return nullptr;
}

(六)线程退出函数

//线程退出
template <typename T>
void ThreadPool<T>::threadExit()
{pthread_t tid = pthread_self();for (int i = 0; i < maxNum; ++i){if (threadIDs[i] == tid){threadIDs[i] = 0;// std::cout<<"threadExit() called, "<<std::string to_string(tid)<<" exiting..."<<std::endl;printf("threadExit() called,%ld exiting...\n", tid);break;}}pthread_exit(NULL); // 这个是标准C的函数
}

(七)管理者线程的任务函数

// 管理者线程任务函数
template <typename T>
void *ThreadPool<T>::manager(void *arg)
{ThreadPool *pool = static_cast<ThreadPool *>(arg);// 如果线程池没有关闭,就一直检测while (!pool->shutdown){// 每隔3s检测一次sleep(3);// 取出线程池中任务的数量和当前线程的数量 取出(工作的)忙的线程的数量pthread_mutex_lock(&pool->mutexPool);int queueSize = pool->taskQ->taskNumber();int liveNum = pool->liveNum;int busyNum = pool->busyNum;pthread_mutex_unlock(&pool->mutexPool);// 添加线程// 任务的个数>存货的线程个数 && 存活的线程数 < 最大线程数if (queueSize > liveNum && liveNum < pool->maxNum){pthread_mutex_lock(&pool->mutexPool);int counter = 0;for (int 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 (int i = 0; i < NUMBER; ++i){pthread_cond_signal(&pool->notEmpty);}}}return nullptr;
}

(八)给线程池添加任务

// 给线程池添加任务
template <typename T>
void ThreadPool<T>::addTask(Task<T> task)
{if (shutdown){return;}// 添加任务,不需要加锁,任务队列中有锁taskQ->addTask(task);// 唤醒工作的线程pthread_cond_signal(&notEmpty); // 通知消费者消费
}

(九)获取线程池忙的线程个数和活着的线程个数

template <typename T>
int ThreadPool<T>::getBusyNum()
{pthread_mutex_lock(&mutexPool);int busyNum = this->busyNum;pthread_mutex_unlock(&mutexPool);return busyNum;
}template <typename T>
int ThreadPool<T>::getAliveNum()
{pthread_mutex_lock(&mutexPool);int aliveNum = this->liveNum;pthread_mutex_unlock(&mutexPool);return aliveNum;
}

(十)线程池析构

template <typename T>
ThreadPool<T>::~ThreadPool()
{// 关闭线程池shutdown = true;// 阻塞回收管理者线程pthread_join(managerID, NULL);// 唤醒阻塞的消费者线程for (int i = 0; i < liveNum; i++){pthread_cond_signal(&notEmpty);}// 释放堆内存if (taskQ) delete taskQ;if (threadIDs) delete[] threadIDs;pthread_mutex_destroy(&mutexPool);pthread_cond_destroy(&notEmpty);
}

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

完整代码:

TaskQueue.h

#pragma once
#include <queue>
#include <pthread.h>using callback = void (*)(void *);// 任务结构体
template <typename T>
struct Task
{Task<T>(){function = nullptr;arg = nullptr;}Task<T>(callback f, void *arg){this->arg = (T *)arg;function = f;}callback function;T *arg;
};template <typename T>
class TaskQueue
{
public:TaskQueue();~TaskQueue();// 添加任务void addTask(Task<T> task);void addTask(callback f, void *arg);// 取出一个任务Task<T> takeTask();// 获取当前任务的个数inline size_t taskNumber(){return m_taskQ.size();}private:std::queue<Task<T> > m_taskQ;pthread_mutex_t m_mutex;
};

TaskQueue.cpp

#include "TaskQueue.h"
#include <pthread.h>template <typename T>
TaskQueue<T>::TaskQueue()
{// 初始化互斥锁pthread_mutex_init(&m_mutex, NULL);
}template <typename T>
TaskQueue<T>::~TaskQueue()
{// 销毁互斥锁pthread_mutex_destroy(&m_mutex);
}template <typename T>
void TaskQueue<T>::addTask(Task<T> task)
{pthread_mutex_lock(&m_mutex);m_taskQ.push(task);pthread_mutex_unlock(&m_mutex);
}template <typename T>
Task<T> TaskQueue<T>::takeTask()
{Task<T> t;pthread_mutex_lock(&m_mutex);if (!m_taskQ.empty()){t = m_taskQ.front();m_taskQ.pop();}pthread_mutex_unlock(&m_mutex);return t;
}template <typename T>
void TaskQueue<T>::addTask(callback f, void *arg)
{pthread_mutex_lock(&m_mutex);m_taskQ.push(Task<T>(f, arg));pthread_mutex_unlock(&m_mutex);
}

ThreadPool.h

#pragma once
#include "TaskQueue.h"
template <typename T>
class ThreadPool
{
public:// 创建线程池并初始化ThreadPool(int min, int max);// 销毁线程池~ThreadPool();// 给线程池添加任务void addTask(Task<T> task);// 获取线程池中工作的线程的个数int getBusyNum();// 获取线程池中活着的线程个数int getAliveNum();private:// 工作的线程是(消费者线程)任务函数static void *worker(void *arg);// 管理者线程任务函数static void *manager(void *arg);// 单个线程退出void threadExit();private:// 任务队列TaskQueue<T> *taskQ;pthread_t managerID;       // 管理者线程IDpthread_t *threadIDs;      // 工作的线程IDint minNum;                // 最小线程数量int maxNum;                // 最大线程数量int busyNum;               // 忙的线程的个数int liveNum;               // 存活的线程的个数int exitNum;               // 要销毁的线程个数pthread_mutex_t mutexPool; // 锁整个的线程池pthread_cond_t notEmpty;   // 任务队列是不是空了bool shutdown = false; // 是不是要销毁线程池static const int NUMBER = 2;
};

ThreadPool.cpp

#include "ThreadPool.h"
#include <iostream>
#include <string.h>
#include <stdlib.h>#include <unistd.h>template <typename T>
ThreadPool<T>::ThreadPool(int min, int max)
{do{// 实例化任务队列taskQ = new TaskQueue<T>;if (taskQ == nullptr){std::cout << "new taskQ fail..." << std::endl;break;}// 根据线程的最大上限给线程数组分配内存threadIDs = new pthread_t[max];if (threadIDs == nullptr){std::cout << "new threadIDs fail..." << std::endl;break;}// 初始化memset(threadIDs, 0, sizeof(pthread_t) * max);minNum = min;maxNum = max;busyNum = 0;liveNum = min; // 和最小个数相等exitNum = 0;// 初始化互斥锁,条件变量if (pthread_mutex_init(&mutexPool, NULL) != 0 ||pthread_cond_init(&notEmpty, NULL) != 0){std::cout << "mutex or condition init fail...\n"<< std::endl;break;}shutdown = false;/// 创建线程 ////"void *(ThreadPool::*)(void *arg)" 类型的实参与 "void *(*)(void *)" 类型的形参不兼容// 类的静态成员或者类的外部的非类普通函数,只要定义出来之后就有地址// 如果是类的成员函数,并且不是静止的,这个函数定义出来是没有地址的// 什么时候有地址呢?我们必须要给这个类进行实例化,创建类的对象,这个// 函数才有地址// this指针指向当前被实例化的对象,如果要是在外边new出来一个ThreadPool对象,// 那么这个this指针就指向该实例化对象// 为什么要把this传给manager呢?// 因为manager是一个静态方法,静态方法它只能访问类里边的静态成员变量,// 它不能访问类的非静态成员变量。// 因此如果我们想要访问这些非静态成员变量,就必须要给这个静态方法传进去一个// 实例化对象,通过传进去的这个实例化对象来访问里边的非静态成员函数和变量// 创建管理者线程,1个pthread_create(&managerID, NULL, manager, this);// 根据最小线程个数,创建线程for (int i = 0; i < min; i++){pthread_create(&threadIDs[i], NULL, worker, this);}return;} while (0);// 释放资源if (threadIDs)delete[] threadIDs;if (taskQ)delete taskQ;
}// 工作线程任务函数
template <typename T>
void *ThreadPool<T>::worker(void *arg)
{ThreadPool *pool = static_cast<ThreadPool *>(arg);// 一直不停的工作while (true){// 访问任务队列(共享资源)加锁pthread_mutex_lock(&pool->mutexPool);// 当前任务队列是否为空,如果为空工作线程阻塞while (pool->taskQ->taskNumber() == 0 && !pool->shutdown){//printf("thread %ld waiting...\n",pthread_self());// 阻塞工作线程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);pool->threadExit();}}}// 判断线程池是否被关闭了if (pool->shutdown){pthread_mutex_unlock(&pool->mutexPool);pool->threadExit();}// 从任务队列中取出一个任务Task<T> task = pool->taskQ->takeTask();// 工作的线程+1pool->busyNum++;// 线程池解锁pthread_mutex_unlock(&pool->mutexPool);// 执行任务printf("thread %ld start working...\n", pthread_self());// std::cout<<"thread "<<to_string(pthread_self()) <<" start working..."<<std::endl;task.function(task.arg);delete task.arg;task.arg = nullptr;// 任务处理结束printf("thread %ld end working...\n", pthread_self());// std::cout<<"thread "<<std::string to_string(pthread_self()) <<" end working..."<<std::endl;pthread_mutex_lock(&pool->mutexPool);pool->busyNum--;pthread_mutex_unlock(&pool->mutexPool);}return nullptr;
}//线程退出
template <typename T>
void ThreadPool<T>::threadExit()
{pthread_t tid = pthread_self();for (int i = 0; i < maxNum; ++i){if (threadIDs[i] == tid){threadIDs[i] = 0;// std::cout<<"threadExit() called, "<<std::string to_string(tid)<<" exiting..."<<std::endl;printf("threadExit() called,%ld exiting...\n", tid);break;}}pthread_exit(NULL); // 这个是标准C的函数
}// 管理者线程任务函数
template <typename T>
void *ThreadPool<T>::manager(void *arg)
{ThreadPool *pool = static_cast<ThreadPool *>(arg);// 如果线程池没有关闭,就一直检测while (!pool->shutdown){// 每隔3s检测一次sleep(3);// 取出线程池中任务的数量和当前线程的数量 取出(工作的)忙的线程的数量pthread_mutex_lock(&pool->mutexPool);int queueSize = pool->taskQ->taskNumber();int liveNum = pool->liveNum;int busyNum = pool->busyNum;pthread_mutex_unlock(&pool->mutexPool);// 添加线程// 任务的个数>存货的线程个数 && 存活的线程数 < 最大线程数if (queueSize > liveNum && liveNum < pool->maxNum){pthread_mutex_lock(&pool->mutexPool);int counter = 0;for (int 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 (int i = 0; i < NUMBER; ++i){pthread_cond_signal(&pool->notEmpty);}}}return nullptr;
}// 给线程池添加任务
template <typename T>
void ThreadPool<T>::addTask(Task<T> task)
{if (shutdown){return;}// 添加任务,不需要加锁,任务队列中有锁taskQ->addTask(task);// 唤醒工作的线程pthread_cond_signal(&notEmpty); // 通知消费者消费
}template <typename T>
int ThreadPool<T>::getBusyNum()
{pthread_mutex_lock(&mutexPool);int busyNum = this->busyNum;pthread_mutex_unlock(&mutexPool);return busyNum;
}template <typename T>
int ThreadPool<T>::getAliveNum()
{pthread_mutex_lock(&mutexPool);int aliveNum = this->liveNum;pthread_mutex_unlock(&mutexPool);return aliveNum;
}template <typename T>
ThreadPool<T>::~ThreadPool()
{// 关闭线程池shutdown = true;// 阻塞回收管理者线程pthread_join(managerID, NULL);// 唤醒阻塞的消费者线程for (int i = 0; i < liveNum; i++){pthread_cond_signal(&notEmpty);}// 释放堆内存if (taskQ) delete taskQ;if (threadIDs) delete[] threadIDs;pthread_mutex_destroy(&mutexPool);pthread_cond_destroy(&notEmpty);
}

main.cpp

#include <iostream>
#include <pthread.h>
#include "ThreadPool.h"
#include "ThreadPool.cpp"
#include "TaskQueue.cpp"
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>void taskFunc(void *arg)
{int num = *(int *)arg;printf("thread %ld is working, number = %d \n", pthread_self(), num);sleep(1);
}int main()
{// 创建线程池ThreadPool<int> pool(3, 10);for (int i = 0; i < 100; ++i){int *num = new int(i + 100);*num = i + 100;pool.addTask(Task<int>(taskFunc, num));}sleep(20);return 0;
}

跟着这个老师的教程学习的,总结的笔记!!! 

手写线程池 - C改C++版 | 爱编程的大丙 (subingwen.cn)icon-default.png?t=N6B9https://subingwen.cn/linux/threadpool-cpp/

相关文章:

手写线程池 - C++版 - 笔记总结

1.线程池原理 创建一个线程&#xff0c;实现很方便。 缺点&#xff1a;若并发的线程数量很多&#xff0c;并且每个线程都是执行一个时间较短的任务就结束了。 由于频繁的创建线程和销毁线程需要时间&#xff0c;这样的频繁创建线程会大大降低 系统的效率。 2.思考 …...

PHP 容器化引发线上 502 错误状态码的修复

最后更新时间 2023-01-24. 背景 笔者所在公司技术栈为 Golang PHP&#xff0c;目前部分项目已经逐步转 Go 语言重构&#xff0c;部分 PHP 业务短时间无法用 Go 重写。 相比 Go 语言&#xff0c;互联网公司常见的 Nginx PHP-FPM 模式&#xff0c;经常会出现性能问题—— 特…...

QT中UDP之UDPsocket通讯

目录 UDP&#xff1a; 举例&#xff1a; 服务器端&#xff1a; 客户端&#xff1a; 使用示例&#xff1a; 错误例子并且改正&#xff1a; UDP&#xff1a; &#xff08;User Datagram Protocol即用户数据报协议&#xff09;是一个轻量级的&#xff0c;不可靠的&#xff0…...

【C语言】10-三大结构之循环结构-1

1. 引言 在日常生活中经常会遇到需要重复处理的问题,例如 统计全班 50 个同学平均成绩的程序求 30 个整数之和检查一个班级的同学程序是否及格要处理以上问题,最原始的方法是分别编写若干个相同或相似的语句或者程序段进行处理 例如:处理 50 个同学的平均成绩可以先计算一个…...

Windows下RocketMQ的启动

下载地址&#xff1a;下载 | RocketMQ 解压后 一、修改runbroker.cmd 修改 bin目录下的runbroker.cmd set "JAVA_OPT%JAVA_OPT% -server -Xms2g -Xmx2g" set "JAVA_OPT%JAVA_OPT% -XX:MaxDirectMemorySize15g" set "JAVA_OPT%JAVA_OPT% -cp %CLASSP…...

linux内核升级 docker+k8s更新显卡驱动

官方驱动 | NVIDIA在此链接下载对应的显卡驱动 # 卸载可能存在的旧版本nvidia驱动(如果没有安装过可跳过&#xff0c;建议执行) sudo apt-get remove --purge nvidia* # 安装驱动需要的依赖 sudo apt-get install dkms build-essential linux-headers-generic sudo vim /etc/mo…...

express学习笔记2 - 三大件概念

中间件 中间件是一个函数&#xff0c;在请求和响应周期中被顺序调用&#xff08;WARNING&#xff1a;提示&#xff1a;中间件需要在响应结束前被调用&#xff09; 路由 应用如何响应请求的一种规则 响应 / 路径的 get 请求&#xff1a; app.get(/, function(req, res) {res…...

Steam搬砖蓝海项目

这个项目早在很久之前就已经存在&#xff0c;并且一直非常稳定。如果你玩过一些游戏&#xff0c;你一定知道Steam是什么平台。Steam平台是全球最大的综合性数字发行平台之一&#xff0c;玩家可以在该平台购买、下载、讨论、上传和分享游戏和软件。 今天我给大家解释一下什么是…...

就业并想要长期发展选数字后端还是ic验证?

“就业并想要长期发展选数字后端还是ic验证&#xff1f;” 这是知乎上的一个热点问题&#xff0c;浏览量达到了13,183。看来有不少同学对这个问题感到疑惑。之前更新了数字后端&数字验证的诸多文章&#xff0c;从学习到职业发展&#xff0c;都写过&#xff0c;唯一没有做过…...

当服务器域名出现解析错误的问题该怎么办?

​  域名解析是互联网用户接收他们正在寻找的域的地址的过程。更准确地说&#xff0c;域名解析是人们在浏览器中输入时使用的域名与网站IP地址之间的转换过程。您需要站点的 IP 地址才能知道它所在的位置并加载它。但&#xff0c;在这个过程中&#xff0c;可能会出现多种因素…...

面试必考精华版Leetcode2095. 删除链表的中间节点

题目&#xff1a; 代码&#xff08;首刷看解析 day22&#xff09;&#xff1a; class Solution { public:ListNode* deleteMiddle(ListNode* head) {if(head->nextnullptr) return nullptr;ListNode *righthead;ListNode *lefthead;ListNode *NodeBeforeLeft;while(right!n…...

对 Redis 实现分布式事务的探索与实现

对 Redis 实现分布式事务的探索与实现 一、简介简介优势 二、Redis 的事务机制事务WATCH 命令MULTI 命令EXEC 命令UNWATCH 命令 三、Redis 的分布式事务集群架构分布式事务分布式事务实现方式1. 两阶段提交&#xff08;2PC&#xff09;方式Paxos 算法实现方式Raft 算法实现方式…...

Matlab实现Spectral Clustering算法

Spectral Clustering算法是一种基于图论的聚类算法&#xff0c;它可以将数据点按照图结构进行划分&#xff0c;发现复杂和非线性可分的结构。在这篇博客中&#xff0c;我将介绍Spectral Clustering算法的原理和步骤&#xff0c;并给出一个用Matlab实现的代码示例。 目录 一、…...

Android 测试

工程目录图 1- Espresso 2- uiautomator Espresso 文档UI Automator文档ui-automator 英文文档 请点击下面工程名称&#xff0c;跳转到代码的仓库页面&#xff0c;将工程 下载下来 Demo Code 里有详细的注释 代码&#xff1a;testespresso 参考文献 Android 利用 espre…...

全面解析大语言模型的工作原理

当ChatGPT在去年秋天推出时&#xff0c;在科技行业乃至世界范围内引起了轰动。当时&#xff0c;机器学习研究人员尝试研发了多年的语言大模型&#xff08;LLM&#xff09;&#xff0c;但普通大众并未十分关注&#xff0c;也没有意识到它们变得多强大。 如今&#xff0c;几乎每个…...

cmake+pybind11打包c++库成python wheel安装包

目录 写在前面准备1、pybind11获取源码编译安装 2、conda demo官方源码修改CMakeLists.txt编译生成安装测试 参考完 写在前面 1、本文内容 有时候我们需要用c代码&#xff0c;供python调用&#xff0c;本文提供将c库封装成python接口的方法&#xff0c;并将库打包成可通过pip安…...

史上最细,接口自动化测试框架-Pytest+Allure+Excel整理(代码)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Allure框架 Allu…...

【计算机视觉中的 GAN 】 - 条件图像合成和 3D 对象生成(2)

一、说明 上文 【计算机视觉中的 GAN 】或多或少是GANs&#xff0c;生成学习和计算机视觉的介绍。我们达到了在 128x128 图像中生成可区分图像特征的程度。但是&#xff0c;如果你真的想了解GAN在计算机视觉方面的进展&#xff0c;你肯定必须深入研究图像到图像的翻译。…...

智安网络|常见的网络安全陷阱:你是否掉入了其中?

在数字化时代&#xff0c;网络安全成为了一个重要的议题。随着我们越来越多地在互联网上进行各种活动&#xff0c;诸如在线银行交易、社交媒体分享和在线购物等&#xff0c;我们的个人信息也更容易受到攻击和滥用。虽然有许多关于网络安全的指导和建议&#xff0c;但仍然有许多…...

亚马逊云科技HPC解决方案,帮助浙江大学实现成本和科研任务的双丰收

浙江大学土壤学科是朱祖祥院士等几代土壤科学家共同创建的A国家重点学科&#xff0c;整体实力雄厚&#xff0c;优势特色明显&#xff0c;总体水平居国内前列。在亚马逊云科技科研创新支持计划&#xff08;Amazon Web Services Cloud Credits for Research&#xff09;的多次支持…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

服务器硬防的应用场景都有哪些?

服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式&#xff0c;避免服务器受到各种恶意攻击和网络威胁&#xff0c;那么&#xff0c;服务器硬防通常都会应用在哪些场景当中呢&#xff1f; 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...

c#开发AI模型对话

AI模型 前面已经介绍了一般AI模型本地部署&#xff0c;直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型&#xff0c;但是目前国内可能使用不多&#xff0c;至少实践例子很少看见。开发训练模型就不介绍了&am…...

tree 树组件大数据卡顿问题优化

问题背景 项目中有用到树组件用来做文件目录&#xff0c;但是由于这个树组件的节点越来越多&#xff0c;导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多&#xff0c;导致的浏览器卡顿&#xff0c;这里很明显就需要用到虚拟列表的技术&…...

【生成模型】视频生成论文调研

工作清单 上游应用方向&#xff1a;控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

LabVIEW双光子成像系统技术

双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制&#xff0c;展现出显著的技术优势&#xff1a; 深层组织穿透能力&#xff1a;适用于活体组织深度成像 高分辨率观测性能&#xff1a;满足微观结构的精细研究需求 低光毒性特点&#xff1a;减少对样本的损伤…...

c++第七天 继承与派生2

这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分&#xff1a;派生类构造函数与析构函数 当创建一个派生类对象时&#xff0c;基类成员是如何初始化的&#xff1f; 1.当派生类对象创建的时候&#xff0c;基类成员的初始化顺序 …...

WEB3全栈开发——面试专业技能点P4数据库

一、mysql2 原生驱动及其连接机制 概念介绍 mysql2 是 Node.js 环境中广泛使用的 MySQL 客户端库&#xff0c;基于 mysql 库改进而来&#xff0c;具有更好的性能、Promise 支持、流式查询、二进制数据处理能力等。 主要特点&#xff1a; 支持 Promise / async-await&#xf…...

2025-05-08-deepseek本地化部署

title: 2025-05-08-deepseek 本地化部署 tags: 深度学习 程序开发 2025-05-08-deepseek 本地化部署 参考博客 本地部署 DeepSeek&#xff1a;小白也能轻松搞定&#xff01; 如何给本地部署的 DeepSeek 投喂数据&#xff0c;让他更懂你 [实验目的]&#xff1a;理解系统架构与原…...