当前位置: 首页 > 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;的多次支持…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

家政维修平台实战20:权限设计

目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系&#xff0c;主要是分成几个表&#xff0c;用户表我们是记录用户的基础信息&#xff0c;包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题&#xff0c;不同的角色&#xf…...

2021-03-15 iview一些问题

1.iview 在使用tree组件时&#xff0c;发现没有set类的方法&#xff0c;只有get&#xff0c;那么要改变tree值&#xff0c;只能遍历treeData&#xff0c;递归修改treeData的checked&#xff0c;发现无法更改&#xff0c;原因在于check模式下&#xff0c;子元素的勾选状态跟父节…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

select、poll、epoll 与 Reactor 模式

在高并发网络编程领域&#xff0c;高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表&#xff0c;以及基于它们实现的 Reactor 模式&#xff0c;为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。​ 一、I…...

全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比

目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec&#xff1f; IPsec VPN 5.1 IPsec传输模式&#xff08;Transport Mode&#xff09; 5.2 IPsec隧道模式&#xff08;Tunne…...

【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统

目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索&#xff08;基于物理空间 广播范围&#xff09;2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...

TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?

在工业自动化持续演进的今天&#xff0c;通信网络的角色正变得愈发关键。 2025年6月6日&#xff0c;为期三天的华南国际工业博览会在深圳国际会展中心&#xff08;宝安&#xff09;圆满落幕。作为国内工业通信领域的技术型企业&#xff0c;光路科技&#xff08;Fiberroad&…...

如何应对敏捷转型中的团队阻力

应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中&#xff0c;明确沟通敏捷转型目的尤为关键&#xff0c;团队成员只有清晰理解转型背后的原因和利益&#xff0c;才能降低对变化的…...