【Linux学习】线程池
目录
23.线程池
23.1 什么是线程池
23.2 为什么需要线程池
23.3 线程池的应用场景
23.4 实现一个简单的线程池
23.4.1 RAII风格信号锁
23.4.2 线程的封装
23.4.3 日志打印
22.4.4 定义队列中存放Task类任务
23.4.5 线程池的实现(懒汉模式)
为什么线程池中需要有互斥锁和条件变量?
为什么线程池中的线程执行例程需要设置为静态方法?
为什么私有化构造函数?(单例模式)
23.4.6 单例模式
饿汉方式实现单例模式
懒汉方式实现单例模式
23.4.7 主程序实现
22.4.8 程序运行效果
23.线程池
23.1 什么是线程池
线程池(Thread Pool)是一种管理和重用线程的机制,在多线程编程中,创建和销毁线程是一项开销较大的操作,因为涉及到操作系统的资源管理和线程上下文切换的成本。线程池通过预先创建一组线程并维护它们,可以避免频繁地创建和销毁线程,从而降低了系统的开销。
- 线程池中的多个已经创建好的线程负责从任务队列当中拿任务,并将拿到的任务进行处理。
- 线程池对外提供一个Push接口,用于让外部将任务Push到任务队列当中
23.2 为什么需要线程池
举个例子:
- 想象你是一个公司的HR经理,有许多员工需要执行不同的任务。每当有任务到来时,你要做的是从员工池中选择一个空闲的员工来执行任务。如果每次任务到来时都去招聘新员工(创建新线程),等任务完成后再解雇他们(销毁线程),那么你将花费大量的时间和精力来管理这个过程,而且频繁的招聘和解雇会带来一定的成本和效率损失。
- 相反,如果你有一个固定数量的员工池(线程池),这些员工已经预先准备好并且处于待命状态。当任务到来时,你只需要从员工池中选择一个空闲的员工分配任务给他,完成后他会继续留在池中等待下一个任务的到来。这样可以避免频繁的人员调配(线程的创建和销毁),节省了时间和成本,提高了效率。
- 因此,线程池就像是一个员工池,能够有效地管理和利用资源,提高系统的性能和效率。
当需要处理大量的并发任务时,使用线程池能够提高系统的效率和性能 :
- 减少线程创建开销:线程池通过维护一定数量的预创建线程,避免了频繁创建和销毁线程所带来的系统资源消耗。这样一来,系统能够更有效地利用可用的处理器资源和内存,提高整体的资源利用率。
- 任务队列管理:线程池通常配备了任务队列,用于存储需要执行的任务。通过合理管理任务队列,可以实现任务的调度和分配,保证任务的有序执行,提高系统的整体性能。
- 提高代码可维护性:使用线程池能够将线程管理的逻辑与业务逻辑分离,使代码更加清晰简洁,降低了系统的复杂度,提高了代码的可维护性和可读性。
- 控制并发度:线程池允许开发者限制同时执行的线程数量,从而避免系统资源被过度占用,防止系统负载过重,提高系统的稳定性和可靠性。
23.3 线程池的应用场景
线程池适合应对那些需要异步执行、并发处理的任务。适合处理以下类型的任务:
- 短时任务(Short-lived Tasks):短时任务是指执行时间较短的任务,通常是一些简单的计算、请求响应等操作。线程池可以有效地管理和复用线程,避免频繁创建和销毁线程的开销,从而更快地处理这类任务。
- 事件驱动任务(Event-driven Tasks):事件驱动任务是指需要响应事件触发的任务,如GUI应用程序中的事件处理、网络服务器中的请求处理等。线程池可以用来处理这些事件,保持系统的响应速度和并发处理能力。
- IO密集型任务(IO-bound Tasks):IO密集型任务是指任务主要涉及到IO操作,如文件读写、网络请求等。由于IO操作通常会阻塞线程,使用线程池可以将阻塞操作委托给后台线程处理,从而提高系统的并发处理能力。
这使得线程池在许多不同的应用场景中都能发挥重要作用。以下是一些常见的应用场景:
-
Web 开发:在 Web 开发中,常常需要处理大量的并发请求,如处理用户请求、数据库查询等。通过使用线程池,可以提高服务器的并发处理能力,减少请求的响应时间。
-
后台任务处理:在后台任务处理中,如批量数据处理、定时任务调度等,使用线程池可以更有效地管理和调度任务,提高系统的性能和效率。
-
图像处理和计算密集型任务:在图像处理、视频编解码、科学计算等领域,常常需要进行大量的计算密集型任务。使用线程池可以并行地处理这些任务,提高处理速度和效率。
-
数据库连接管理:在数据库访问中,通过使用线程池管理数据库连接,可以减少数据库连接的创建和销毁开销,提高数据库访问的性能和效率。
-
并行编程框架:在并行编程框架中,如 Java 的 Fork/Join 框架、Python 的 concurrent.futures 模块等,线程池常常被用来并行地执行任务,以提高程序的性能和扩展性。
23.4 实现一个简单的线程池
23.4.1 RAII风格信号锁
我们可以定义了一个 lockGuard
类,采用 RAII(资源获取即初始化)方式,对互斥锁进行加锁和解锁,确保在作用域结束时自动释放锁。
这里我们创建一个名为lock.hpp:的文件来定义lockGuard类
#pragma once
#include <iostream>
#include <pthread.h>
class lockGuard
{
public:lockGuard(pthread_mutex_t *mtx) : mtx_(mtx){pthread_mutex_lock(mtx_);}~lockGuard(){pthread_mutex_unlock(mtx_);}private:pthread_mutex_t *mtx_; // 指向要管理的互斥锁的指针
};
在lockGuard
类的构造函数中,首先通过传入的pthread_mutex_t
类型的指针初始化mtx_
成员变量,即指向要管理的互斥锁。然后调用pthread_mutex_lock
函数对该互斥锁进行加锁操作。
在lockGuard
类的析构函数中,调用pthread_mutex_unlock
函数对互斥锁进行解锁操作。由于该析构函数在对象生命周期结束时自动调用,因此实现了互斥锁的自动释放。这样,在使用lockGuard
对象时,只需要在作用域中创建该对象,当对象离开作用域时,析构函数会自动调用,从而释放互斥锁,确保了互斥锁的安全管理。
23.4.2 线程的封装
这里我们创建一个名为Thread.hpp的文件来定义Thread类,实现对其的封装.
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <cstdio>
#include <pthread.h> // 使用 pthread 相关函数,需要包含 pthread.h 头文件// 定义一个函数指针类型,用于作为线程的回调函数
typedef void *(*fun_t)(void *);// 线程的数据结构,用于传递给线程执行函数
class ThreadData
{
public:void *args_; // 线程执行函数的参数std::string name_; // 线程的名称
};// 线程类,用于创建和管理线程
class Thread
{
public:// 构造函数,初始化线程对象Thread(int num, fun_t callback, void *args) : func_(callback){char nameBuffer[64];snprintf(nameBuffer, sizeof nameBuffer, "Thread-%d", num);name_ = nameBuffer;tdata_.args_ = args;tdata_.name_ = name_;}// 启动线程void start(){pthread_create(&tid_, nullptr, func_, (void *)&tdata_);}// 等待线程结束void join(){pthread_join(tid_, nullptr);}// 返回线程的名称std::string name(){return name_;}// 析构函数~Thread(){}private:std::string name_; // 线程名称fun_t func_; // 线程执行的回调函数ThreadData tdata_; // 线程的数据pthread_t tid_; // 线程ID
};
23.4.3 日志打印
这里我们创建一个名为Log.hpp的文件来实现日志打印保存
#pragma once
#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <string>
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
const char *gLevelMap[] = {"DEBUG","NORMAL","WARNING","ERROR","FATAL"
};
#define LOGFILE "./threadpool.log"
void logMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOWif(level== DEBUG) return;
#endifchar stdBuffer[1024]; //标准部分time_t timestamp = time(nullptr);snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", gLevelMap[level], timestamp);char logBuffer[1024]; //自定义部分va_list args;va_start(args, format);vsnprintf(logBuffer, sizeof logBuffer, format, args);va_end(args);FILE *fp = fopen(LOGFILE, "a");fprintf(fp, "%s%s\n", stdBuffer, logBuffer);fclose(fp);
}
- 这段代码定义了一个函数 logMessage,用于记录日志到一个指定的文件中。
- 日志级别被定义为 DEBUG、NORMAL、WARNING、ERROR 和 FATAL,不同级别的日志会按照不同的格式输出到日志文件中。
- 日志文件名被定义为 ./threadpool.log 。
- 编译时如果未显示定义 -DEBUG_SHOW ,则不会将DEBUG 级别的日志打印进文件
va_list args;
va_start(args, format);
vsnprintf(logBuffer, sizeof logBuffer, format, args);
va_end(args);
这段代码片段使用了 C 语言的可变参数函数和相关的宏来格式化日志消息。
- va_list 是一个类型,用于声明一个指向参数列表的变量。在这里,args 是一个 va_list 类型的变量,用于存储可变参数列表。
- va_start 宏初始化 args 变量,使其指向参数列表中的第一个可变参数。在这里,args 变量将指向传递给 logMessage 函数的第一个可变参数。
- vsnprintf 函数将可变参数列表按照指定的格式 format 进行格式化,并将结果存储到 logBuffer 中。它的功能类似于 sprintf 函数,但是可以处理可变参数列表。
- va_end 宏清理 args 变量,结束对可变参数列表的访问。在这里,它通知编译器该可变参数列表已经处理完毕,可以进行清理工作。
22.4.4 定义队列中存放Task类任务
这里我们创建一个名为Task.hpp的文件来定义Task类
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include "Log.hpp"
typedef std::function<int(int, int)> func_t;
class Task
{
public:Task(){}Task(int x, int y, func_t func):x_(x), y_(y), func_(func){}void operator ()(const std::string &name){logMessage(WARNING, "%s处理完成: %d+%d=%d | %s | %d",name.c_str(), x_, y_, func_(x_, y_), __FILE__, __LINE__);}
public:int x_;int y_;func_t func_;
};
23.4.5 线程池的实现(懒汉模式)
我们需要实现Push接口向任务队列里面放任务,又要Pop任务给已经创建好的线程,其本质就是一个生产者-消费者模型
这里我们创建一个名为ThreadPool.hpp的文件来定义ThreadPool类
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <unistd.h>
#include "Thread.hpp" // 导入线程类头文件
#include "Lock.hpp" // 导入锁相关的头文件
#include "Log.hpp" // 导入日志相关的头文件const int g_thread_num = 3; // 默认线程池中线程数量// 线程池类模板
template <class T>
class ThreadPool
{
public:pthread_mutex_t *getMutex() // 获取互斥锁指针{return &lock;}bool isEmpty() // 判断任务队列是否为空{return task_queue_.empty();}void waitCond() // 等待条件变量{pthread_cond_wait(&cond, &lock);}T getTask() // 获取任务{T t = task_queue_.front(); // 获取队首任务task_queue_.pop(); // 移除队首任务return t;}
private:ThreadPool(int thread_num = g_thread_num) : num_(thread_num) // 构造函数{pthread_mutex_init(&lock, nullptr); // 初始化互斥锁pthread_cond_init(&cond, nullptr); // 初始化条件变量for (int i = 1; i <= num_; i++){threads_.push_back(new Thread(i, routine, this)); // 创建线程对象并加入线程池}}ThreadPool(const ThreadPool<T> &other) = delete; // 禁止拷贝构造函数const ThreadPool<T> &operator=(const ThreadPool<T> &other) = delete; // 禁止赋值运算符public:static ThreadPool<T> *getThreadPool(int num = g_thread_num) // 获取线程池单例{if (nullptr == thread_ptr) // 如果线程池指针为空{lockGuard lockguard(&mutex); // 创建锁保护if (nullptr == thread_ptr){thread_ptr = new ThreadPool<T>(num); // 创建线程池对象}}return thread_ptr; // 返回线程池指针}void Threadinit() // 初始化线程池中的线程{for (auto &iter : threads_) // 遍历线程池中的线程对象{iter->start(); // 启动线程logMessage(NORMAL, "%s %s", iter->name().c_str(), "启动成功"); // 记录日志}}static void *routine(void *args) // 线程执行函数{ThreadData *td = (ThreadData *)args; // 获取线程数据ThreadPool<T> *tp = (ThreadPool<T> *)td->args_; // 获取线程池指针while (true){T task;{lockGuard lockguard(tp->getMutex()); // 加锁while (tp->isEmpty()) // 如果任务队列为空tp->waitCond(); // 等待条件变量task = tp->getTask(); // 获取任务}task(td->name_); // 执行任务}}void pushTask(const T &task) // 添加任务到任务队列{lockGuard lockguard(&lock); // 加锁task_queue_.push(task); // 添加任务到队列pthread_cond_signal(&cond); // 发送信号唤醒线程}~ThreadPool() // 析构函数{for (auto &iter : threads_) // 遍历线程池中的线程对象{iter->join(); // 等待线程结束delete iter; // 删除线程对象}pthread_mutex_destroy(&lock); // 销毁互斥锁pthread_cond_destroy(&cond); // 销毁条件变量}
private:std::vector<Thread *> threads_; // 线程对象的容器int num_; // 线程数量std::queue<T> task_queue_; // 任务队列static ThreadPool<T> *thread_ptr; // 线程池指针static pthread_mutex_t mutex; // 互斥锁pthread_mutex_t lock; // 互斥锁pthread_cond_t cond; // 条件变量
};// 初始化线程池指针为nullptr
template <typename T>
ThreadPool<T> *ThreadPool<T>::thread_ptr = nullptr;// 初始化互斥锁
template <typename T>
pthread_mutex_t ThreadPool<T>::mutex = PTHREAD_MUTEX_INITIALIZER;
为什么线程池中需要有互斥锁和条件变量?
- 线程池中的任务队列是会被多个执行流同时访问的临界资源,因此我们需要引入互斥锁对任务队列进行保护。
- 线程池当中的线程要从任务队列里拿任务,前提条件是任务队列中必须要有任务,因此线程池当中的线程在拿任务之前,需要先判断任务队列当中是否有任务,若此时任务队列为空,那么该线程应该进行等待,直到任务队列中有任务时再将其唤醒,因此我们需要引入条件变量。
- 当外部线程向任务队列中Push一个任务后,此时可能有线程正处于等待状态,因此在新增任务后需要唤醒在条件变量下等待的线程。
注意:
-
使用
while
进行条件判断:在使用条件变量等待线程唤醒时,应该使用while
循环而不是if
语句进行条件判断。这是因为唤醒线程可能会出现伪唤醒(spurious wakeup)的情况,或者是其他导致唤醒的条件。使用while
循环可以确保线程在被唤醒后重新检查条件,以防止出现虚假唤醒的情况。 -
避免惊群效应:确实,使用
pthread_cond_broadcast
可能会导致惊群效应,即一次性唤醒大量线程,而实际上只有一个线程能够获取到任务。为了避免这种情况,最好使用pthread_cond_signal
函数唤醒一个等待的线程即可,这样可以减少不必要的竞争和资源浪费。 -
在解锁之后处理任务:正确的做法是在拿到任务后先解锁,然后再处理任务。这样可以确保其他线程能够及时获取到任务并开始处理,而不会因为一个线程长时间持有锁而导致其他线程无法执行的情况。将任务处理过程放到解锁之前可能会导致资源的浪费和效率降低。
-
避免串行执行:如果将处理任务的过程放到临界区当中,那么当某一线程从任务队列拿到任务后,其他线程还需要等待该线程将任务处理完后,才有机会进入临界区。此时虽然是线程池,但最终我们可能并没有让多线程并行的执行起来。
为什么线程池中的线程执行例程需要设置为静态方法?
当使用pthread_create
函数创建线程时,需要提供一个C风格的函数指针作为线程的执行例程。然而,在C++中,类的成员函数会隐式地接收一个指向该类对象的指针作为第一个参数(即this
指针)。因此,如果直接将类的成员函数作为线程的执行例程,会导致函数签名不匹配,无法通过编译。
为了解决这个问题,一种常见的方法是将线程执行例程设置为静态方法。静态方法不会隐式接收this
指针作为参数,而是直接通过类名调用。这样,就可以在pthread_create
函数中传递一个静态成员函数的指针作为线程的执行例程。同时,静态方法也具有与类对象实例无关的特性,因此可以被多个线程共享。
然而,静态方法无法直接访问非静态成员函数和变量。如果在执行例程中需要访问类的非静态成员函数或变量(比如Pop),可以通过在创建线程时向执行例程传递类对象的指针来实现。这样,在执行例程中就可以使用该指针来调用类的非静态成员函数或访问非静态成员变量。
为什么私有化构造函数?(单例模式)
私有化构造函数的主要原因是确保线程池类 ThreadPool<T>
的单例模式。通过私有化构造函数,外部无法直接创建新的线程池实例,而是必须通过静态方法 getThreadPool()
来获取线程池的唯一实例。
23.4.6 单例模式
单例(Singleton)模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例 ;
使用场景:
- 语义上只需要一个
- 该对象内部存在大量的空间,保存了大量的数据,如果允许该对象存在多份,或者允许发生各种拷贝,内存中存在冗余数据;
一般Singleton模式通常有三种形式:
- 饿汉方式:吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭。
- 懒汉方式:吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式。
懒汉方式最核心的思想是 "延时加载"。(例如我们之前所学过的写时拷贝)从而能够优化服务器的启动速度。
饿汉方式实现单例模式
template <typename T>
class Singleton
{
private:static Singleton<T> data;//饿汉模式,在加载的时候对象就已经存在了
public: static Singleton<T>* GetInstance() { return &data; }
};
该模式能简单快速的创建一个单例对象,而且是线程安全的(只在类加载时才会初始化,以后都不会)。但它有一个缺点,就是不管你要不要都会直接创建一个对象,会消耗一定的性能(当然很小很小,几乎可以忽略不计,所以这种模式在很多场合十分常用而且十分简单)
懒汉方式实现单例模式
该模式只在你需要对象时才会生成单例对象(比如调用GetInstance方法)
template <typename T>
class Singleton
{
private:static Singleton<T>* inst; //懒汉式单例,只有在调用GetInstance时才会实例化一个单例对象
public: static Singleton<T>* GetInstance() { if (inst == NULL) { inst = new Singleton<T>(); }return inst; }
};
看上去,这段代码没什么明显问题,但它不是线程安全的。假设当前有多个线程同时调用GetInstance()方法,由于当前还没有对象生成,那么就会由多个线程创建多个对象。
// 懒汉模式, 线程安全
template <typename T>
class Singleton
{
private: static Singleton<T>* inst; static std::mutex lock;
public: static T* GetInstance() { if (inst == NULL) // 双重判定空指针, 降低锁冲突的概率, 提高性能 { lock.lock(); // 使用互斥锁, 保证多线程情况下也只调用一次 newif (inst == NULL) { inst = new T(); }lock.unlock(); }return inst;}
};
这种形式是在懒汉方式的基础上增加的,当多个线程调用GetInstance方法时,此时类中没有对象,那么多个线程就会来到锁的位置,竞争锁。必然只能有一个线程竞争锁成功,此时再次判断有没有对象被创建(就是inst指针),如果没有就会new一个对象,如果有就会解锁,并返回已有的对象;总的来说,这样的形式使得多个线程调用GetInstance方法时,无论成功与否,都会有返回值
23.4.7 主程序实现
#include"Log.hpp"
#include"ThreadPool.hpp"
#include"Task.hpp"
#include <ctime>
#include <cstdlib>
#include <iostream>
#include <unistd.h>
int main()
{srand((unsigned long)time(nullptr) ^ getpid());ThreadPool<Task>::getThreadPool()->Threadinit();while(true){int x = rand()%100 + 1;usleep(7721);int y = rand()%30 + 1;Task t(x, y, [](int x, int y)->int{return x + y;});logMessage(DEBUG, "制作任务完成: %d+%d=?", x, y);logMessage(DEBUG, "制作任务完成: %d+%d=?", x, y);logMessage(DEBUG, "制作任务完成: %d+%d=?", x, y);logMessage(DEBUG, "制作任务完成: %d+%d=?", x, y);ThreadPool<Task>::getThreadPool()->pushTask(t);sleep(1);}return 0;
}
22.4.8 程序运行效果
程序运行后,我们打开路径下自动新建得threadpool.log文件,可以看到程序自动打印的日志
相关文章:

【Linux学习】线程池
目录 23.线程池 23.1 什么是线程池 23.2 为什么需要线程池 23.3 线程池的应用场景 23.4 实现一个简单的线程池 23.4.1 RAII风格信号锁 23.4.2 线程的封装 23.4.3 日志打印 22.4.4 定义队列中存放Task类任务 23.4.5 线程池的实现(懒汉模式) 为什么线程池中需要有互斥锁和条件变…...

利用Docker部署一个简单的springboot项目
文章目录 1、首先利用docker部署一个redis中间件1.1、下载redis镜像1.2、在主机创建redis挂载的目录和文件1.3、部署redis中间件 2、创建springboot项目2.1、修改application.yml2.2、编写controller2.3、启动应用并测试访问 3、将应用打包成镜像3.1、编写Dockerfile3.2、上传文…...

【Java】纯小白的三种工厂模式基础知识学习笔记
工厂模式概念 在Java中,工厂模式是一种设计模式,用于创建对象而无需指定明确的类。工厂模式通过定义一个共同的接口或抽象类来创建对象,然后由工厂类根据特定条件或参数来实例化具体的对象。 工厂模式通常包括三种类型:简单工厂…...

Spring Boot 笔记 006 创建接口_注册
1.1 由于返回数据都是以下这种格式,那么久再编写一个result实体类 报错了,原因是没有构造方法 可以使用lombok的注解自动生成,添加无参的构造器和全参的构造器 package com.geji.pojo;import lombok.AllArgsConstructor; import lombok.NoArg…...

沁恒CH32V30X学习笔记08---基本定时器超时功能
TIM 基本定时器 高级定时器模块包含一个功能强大的 16 位自动重装定时器(TIM1、TIM8、TIM9 和 TIM10) 通用定时器模块包含一个 16 位可自动重装的定时器(TIM2、TIM3、TIM4 和 TIM5) 基本定时器模块包含一个 16 位可自动重装的定时器(TIM6 和 TIM7) 定时器的结构大致可…...

GitHub | 在 GitHub 上在线展示 Vue 项目
简洁版:上传所有代码 << 构建项目并上传 dist 目录 << 设置仓库 << 访问 Step1:在 GitHub 上新建仓库,并将 Vue 项目的代码 push 到该仓库中。坑点在于,如果你是从 GitHub 上 clone 的别人的项目,那…...

Android的Compose
Jetpack Compose 是用于构建原生 Android 界面的新工具包,无需修改任何 XML 布局,也不需要使用布局编辑器。相反,只需调用可组合函数来定义所需的元素,Compose 编译器即会完成后面的所有工作。 简而言之,使用Compose&…...

C++ STL->list模拟实现
theme: smartblue list list文档 list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个元素…...

基于python+django+vue.js开发的健身房管理系统
功能介绍 平台采用B/S结构,后端采用主流的Python语言进行开发,前端采用主流的Vue.js进行开发。 功能包括:教练管理、会员管理、场地管理、设备管理、用户管理、日志管理、系统信息模块。 源码地址 https://github.com/geeeeeeeek/python_…...

GPT-4对编程开发的支持
在编程开发领域,GPT-4凭借其强大的自然语言理解和代码生成能力,能够深刻理解开发者的意图,并基于这些需求提供精准的编程指导和解决方案。对于开发者来说,GPT-4能够在代码片段生成、算法思路设计、模块构建和原型实现等方面给予开…...

“成像光谱遥感技术中的AI革命:ChatGPT应用指南“
遥感技术主要通过卫星和飞机从远处观察和测量我们的环境,是理解和监测地球物理、化学和生物系统的基石。ChatGPT是由OpenAI开发的最先进的语言模型,在理解和生成人类语言方面表现出了非凡的能力。本课程重点介绍ChatGPT在遥感中的应用,人工智…...

12.25 校招 实习 内推 面经
绿*泡*泡VX: neituijunsir 交流*裙 ,内推/实习/校招汇总表格 1、校招 | 百度2024校园招聘持续热招中 校招 | 百度2024校园招聘持续热招中 2、校招 | 毫末智行2024秋招补录进行时 校招 | 毫末智行2024秋招补录进行时 3、实习丨蔚来2024届冬季实习生计…...

深度学习基础之《TensorFlow框架(3)—TensorBoard》
一、TensorBoard可视化学习 1、TensorFlow有一个亮点就是,我们能看到自己写的程序的可视化效果,这个功能就是TensorBoard 2、TensorFlow可用于训练大规模深度神经网络所需的计算,使用该工具涉及的计算往往复杂而深奥。为了方便TensorFlow程…...

杨氏矩阵和杨辉三角
杨氏矩阵 有一个数字矩阵,矩阵的每行从左到右是递增的,矩阵从上到下是递增的,请编写程序在这样的矩阵中查找某个数字是否存在。 要求:时间复杂度小于O(N); 分析 若要满足要求时间复杂度小于O(N),就不能每一行一个个…...

PostgreSQL教程(四):高级特性
一、简介 在之前的章节里我们已经涉及了使用SQL在PostgreSQL中存储和访问数据的基础知识。现在我们将要讨论SQL中一些更高级的特性,这些特性有助于简化管理和防止数据丢失或损坏。最后,我们还将介绍一些PostgreSQL扩展。 本章有时将引用教程࿰…...

168基于matlab的六自由度并联摇摆台的反解控制算法
基于matlab的六自由度并联摇摆台的反解控制算法,stewart平台,配有GUI界面,可以自定义角度,杆长等参数。设定动平台位姿即能得到电机参数。程序已调通,可直接运行。 168 六自由度并联摇摆台 反解控制算法 (xiaohongshu.…...

MDC 日志跟踪笔记
一、前言 本文主要解析应用中比较实在的一个log4j的链路应用,在经过一段时间的应用后发现还是很稳的,就记录一下这个MDC 代表Mapped Diagnostic Context(映射式诊断上下文)的情况。 Tisp: 它是一个线程安全的存放诊断日志的容器 T…...

MySQL错误-this is incompatible with sql_mode=only_full_group_by完美解决方案
项目场景 有时候,遇到数据库重复数据,需要将数据进行分组,并取出其中一条来展示,这时就需要用到group by语句。 但是,如果mysql是高版本,当执行group by时,select的字段不属于group by的字段的…...

人工智能|机器学习——基于机器学习的舌苔检测
代码下载: 基于深度学习的舌苔检测毕设留档.zip资源-CSDN文库 1 研究背景 1.1.研究背景与意义 目前随着人们生活水平的不断提高,对于中医主张的理念越来越认可,对中医的需求也越来越多。在诊断中,中医通过观察人的舌头的舌质、苔…...

SQL查询转化为 Elasticsearch 查询
使用SQL 转化为查询 Elasticsearch 支持 sql 语句转化为 elasticsearch 的 查询语句 第一步: 打开在线转换工具的网页,进入工具页面 第二步:在指定的输入框中输入需要转换的 sql 语句。 您学会了这么简单的办法...

目标检测教程视频指南大全
魔鬼面具-哔哩哔哩视频指南 必看干货系列(建议搞深度学习的小伙伴都看看,特别是图像相关) 深度学习常见实验问题与实验技巧(适用于所有模型,小白初学者必看!)还在迷茫深度学习中的改进实验应该从哪里开始改起的同学,一定要进来看看了!用自身…...

【Linux取经路】文件系统之重定向的实现原理
文章目录 一、再来理解重定向1.1 输出重定向效果演示1.2 重定向的原理1.3 dup21.4 输入重定向效果演示1.5 输入重定向代码实现 二、再来理解标准输出和标准错误2.1 同时对标准输出和标准错误进行重定向2.2 将标准输出和标准错误重定向到同一个文件 三、再看一切皆文件四、结语 …...

JAVA设计模式结构型模式
一、前言 java设计模式主要分为创建型模式,结构型模式和行为型模式。上一篇主要总结了行为型设计模式,本章总结,结构型模式。像创建型模式就不写了,比较简单。大概知道是工厂模式和建造者模式,原型模式就行࿰…...

第4讲引入JWT前后端交互
引入JWT前后端交互 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519); JWT就是一段字符串,用来进行用户身份认证的凭证,该字符串分成三段【头部、载荷、签证】 后端接口测试&…...

基于Java的车辆租赁管理平台/租车系统
功能介绍 平台采用B/S结构,后端采用主流的Springboot框架进行开发,前端采用主流的Vue.js进行开发。 整个平台包括前台和后台两个部分。 前台功能包括:首页、车辆详情、车辆预订、用户中心模块。后台功能包括:车辆管理、分类管理…...

如何升级至ChatGPT Plus:快速指南,ChatGPT的秘密武器GPT4.0是什么?
提到 ChatGPT。想必大家都有所耳闻。自从 2022 年上线以来,就受到国内外狂热的追捧和青睐,上线2个月,月活突破1个亿!!! 而且还在持续上涨中。因为有很多人都在使用 ChatGPT 。无论是各大头条、抖音等 App、…...

【天衍系列 05】Flink集成KafkaSink组件:实现流式数据的可靠传输 高效协同
文章目录 01 KafkaSink 版本&导言02 KafkaSink 基本概念03 KafkaSink 工作原理1.初始化连接2.定义序列化模式3.创建KafkaSink算子4.创建数据源5.将数据流添加到KafkaSink6.内部工作机制 04 KafkaSink参数配置05 KafkaSink 应用依赖06 KafkaSink 快速入门6.1 包结构6.2 项目…...

深度学习之pytorch实现逻辑斯蒂回归
深度学习之pytorch实现逻辑斯蒂回归 解决的问题数学公式logiatic函数损失值 代码与线性回归代码的区别数据损失值构造回归的函数 结果分析 解决的问题 logistic 适用于分类问题,这里案例( y为0和1 ,0和 1 分别代表一类) 于解决二分类…...

有事休假店铺无人看守怎么办?智能远程视频监控系统保卫店铺安全
在春节期间,很多自营店主也得到了久违的假期,虽然很多店主都是长期在店铺中看守,但遇到春节这样的日子,多数人还是选择回乡休假。面对店主休假或有事不能管理店铺时,传统的监控虽然可以做到单一的监控,却仍…...

酷开科技 | 酷开系统壁纸模式,让过年更有氛围感!
在阵阵爆竹声中,家家户户都沉浸在浓浓的年味中。过年,是团圆,是温暖。团团圆圆的日子里,仪式感不可少,换上一张喜气洋洋的电视壁纸吧,寓意幸福一年又一年。打开酷开系统壁纸模式挑选一张年味十足的壁纸&…...