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

Linux知识点 -- Linux多线程(四)

Linux知识点 – Linux多线程(四)

文章目录

  • Linux知识点 -- Linux多线程(四)
  • 一、线程池
    • 1.概念
    • 2.实现
    • 3.单例模式的线程池
  • 二、STL、智能指针和线程安全
    • 1.STL的容器是否是线程安全的
    • 2.智能指针是否是线程安全的
  • 三、其他常见的各种锁
  • 四、读者写者问题
    • 1.读写锁
    • 2.读写锁接口


一、线程池

1.概念

一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

  • 预先申请资源,用空间换时间;
  • 预先申请一批线程,任务到来就处理;
  • 线程池就是一个生产消费模型;

2.实现

thread.hpp
线程封装:

#pragma once#include<iostream>
#include<string>
#include<functional>
#include<cstdio>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); // 直接将_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;
};

lockGuard.hpp
锁的封装,构建对象时直接加锁,对象析构时自动解锁;

#pragma once#include <iostream>
#include <pthread.h>class Mutex
{
public:Mutex(pthread_mutex_t *mtx): _pmtx(mtx){}void lock(){pthread_mutex_lock(_pmtx);}void unlock(){pthread_mutex_unlock(_pmtx);}~Mutex(){}private:pthread_mutex_t *_pmtx;
};class lockGuard
{
public:lockGuard(pthread_mutex_t *mtx): _mtx(mtx){_mtx.lock();}~lockGuard(){_mtx.unlock();}
private:Mutex _mtx;
};

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 4const 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);
}
  • 注:
    (1)提取可变参数
    在这里插入图片描述
    使用宏来提取可变参数:
    在这里插入图片描述
    将可变参数格式化打印到对应地点:
    在这里插入图片描述
    format是打印的格式;
    在这里插入图片描述
    (2)条件编译:
    在这里插入图片描述
    条件编译,不想调试的时候,就不加DEBUG宏,不打印日志信息;
    在这里插入图片描述
    -D:在命令行定义宏 ;

threadPool.hpp

线程池封装:

#include "thread.hpp"
#include <vector>
#include <queue>
#include <unistd.h>
#include "log.hpp"
#include "Task.hpp"
#include "lockGuard.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;}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));// 线程构造传入的this指针,是作为ThreadData结构体的参数的,ThreadData结构体才是routine回调函数的参数}}void run(){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; // 拿到this指针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; // 任务队列pthread_mutex_t _lock;     // 锁pthread_cond_t _cond;      // 条件变量
};
  • 注:
    (1)如果回调函数routine放在thread类里面,由于成员函数会默认传this指针,因此参数识别的时候可能会出错,所以需要设置成静态成员;在这里插入图片描述
    在这里插入图片描述
    (2)如果设置成静态类内方法,这个函数只能使用静态成员,而不能使用其他类内成员;
    可以让routine函数拿到整体对象,在构造线程的时候,routine的参数传入this指针;

    在这里插入图片描述
    在构造函数的初始化列表中是参数的初始化,在下面的函数体中是赋值的过程,因此在函数体中对象已经存在了,就可以使用this指针了;
    (3)类内公有接口让静态成员函数routine通过this指针能够访问类内成员;
    在这里插入图片描述
    testMain.cc
#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>* tp = new ThreadPool<Task>();tp->run();while(true){//生产的时候,只做任务要花时间int x = rand()%100 + 1;usleep(7756);int y = rand()%30 + 1;Task t(x, y, [](int x, int y)->int{return x + y;});logMessage(DEBUG, "制作任务完成:%d+%d=?", x, y);//推送任务到线程池中tp->pushTask(t);sleep(1);}return 0;
}

运行结果:
在这里插入图片描述

3.单例模式的线程池

threadPool.hpp

#include "thread.hpp"
#include <vector>
#include <queue>
#include <unistd.h>
#include "log.hpp"
#include "Task.hpp"
#include "lockGuard.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));// 线程构造传入的this指针,是作为ThreadData结构体的参数的,ThreadData结构体才是routine回调函数的参数}}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);//未来任何一个线程想要获取单例,都必须调用getThreadPool接口//一定会存在大量的申请锁和释放锁的行为,无用且浪费资源if(nullptr == _thread_ptr){_thread_ptr = new ThreadPool<T>(num);}}return _thread_ptr;}void run(){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; // 拿到this指针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;      // 条件变量
};//静态成员在类外初始化
template<class T>
ThreadPool<T>* ThreadPool<T>::_thread_ptr = nullptr;template<class T>
pthread_mutex_t ThreadPool<T>::_mutex = PTHREAD_MUTEX_INITIALIZER;

在这里插入图片描述
在这里插入图片描述
多线程同时调用单例过程,由于创建过程是非原子的,有可能被创建多个对象,是非线程安全的;
需要对创建对象的过程加锁,就可以保证在多线程场景当中获取单例对象;
但是未来任何一个线程想调用单例对象,都必须调用这个成员函数,就会存在大量申请和释放锁的行为;
可以在之间加一个对单例对象指针的判断,若不为空,就不进行对象创建;

在这里插入图片描述
testMain.cc

#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>* tp = new ThreadPool<Task>();//tp->run();    ThreadPool<Task>::getThreadPool()->run();//创建单例对象while(true){//生产的时候,只做任务要花时间int x = rand()%100 + 1;usleep(7756);int y = rand()%30 + 1;Task t(x, y, [](int x, int y)->int{return x + y;});logMessage(DEBUG, "制作任务完成:%d+%d=?", x, y);//推送任务到线程池中ThreadPool<Task>::getThreadPool()->pushTask(t);sleep(1);}return 0;
}

运行结果:
在这里插入图片描述

二、STL、智能指针和线程安全

1.STL的容器是否是线程安全的

不是;
原因是, STL的设计初衷是将性能挖掘到极致,而一旦涉及到加锁保证线程安全,会对性能造成巨大的影响;
而且对于不同的容器,加锁方式的不同,性能可能也不同(例如hash表的锁表和锁桶)。
因此STL默认不是线程安全。如果需要在多线程环境下使用,往往需要调用者自行保证线程安全。

2.智能指针是否是线程安全的

对于unique_ ptr,由于只是在当前代码块范围内生效,因此不涉及线程安全问题;
对于shared_ptr,多个对象需要共用一个引用计数变量,所以会存在线程安全问题.但是标准库实现的时候考虑到了这个问题,基于原子操作(CAS)的方式保证shared_ptr 能够高效,原子的操作弓|用计数;

三、其他常见的各种锁

  • 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等) ,当其他线程想要访问数据时,被阻塞挂起;
  • 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作;
    CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试;
  • 自旋锁
    临界资源就绪的时间决定了线程等待的策略;
    不断检测资源是否就绪就是自旋(轮询检测);
    自旋锁本质就是通过不断检测锁状态,来检测资源是否就绪的方案

    在这里插入图片描述
    互斥锁是检测到资源未就绪,就挂起线程;
    临界资源就绪的时间决定了使用哪种锁;

四、读者写者问题

1.读写锁

在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少,相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门]处理这种多读少写的情况呢?有,那就是读写锁。

  • 读者写者模型与生产消费模型的本质区别:
    生产消费模型中消费者会取走数据,而读者写者模型中读者不会取走数据;

  • 读锁的优先级高

2.读写锁接口

  • 初始化:
    在这里插入图片描述

  • 读者加锁:
    在这里插入图片描述

  • 写者加锁:

在这里插入图片描述
生产消费模型中,生产者和消费者的地位是对等的,这样才能达到最高效的状态
而读写者模型中,写者只有在读者全部退出的时候才能写,是读者优先的,这样就会发生写者饥饿问题;
读者写者问题中读锁的优先级高,是因为这种模型的应用场景为:数据的读取频率非常高,而被修改的频率特别低,这样有助于提升效率;

相关文章:

Linux知识点 -- Linux多线程(四)

Linux知识点 – Linux多线程&#xff08;四&#xff09; 文章目录 Linux知识点 -- Linux多线程&#xff08;四&#xff09;一、线程池1.概念2.实现3.单例模式的线程池 二、STL、智能指针和线程安全1.STL的容器是否是线程安全的2.智能指针是否是线程安全的 三、其他常见的各种锁…...

Java设计模式:四、行为型模式-07:状态模式

文章目录 一、定义&#xff1a;状态模式二、模拟场景&#xff1a;状态模式2.1 状态模式2.2 引入依赖2.3 工程结构2.4 模拟审核状态流转2.4.1 活动状态枚举2.4.2 活动信息类2.4.3 活动服务接口2.4.4 返回结果类 三、违背方案&#xff1a;状态模式3.0 引入依赖3.1 工程结构3.2 活…...

很多应用都是nginx+apache+tomcat

nginx 负责负载均衡&#xff0c;将大量的访问量平衡分配给多个服务器 apache 是用来处理静态html、图片等资源&#xff0c;在对HTML解析、响应等方面比tomcat效率更高。 tomcat 处理JSP等内容&#xff0c;进行后台业务操作。 upstream bbb.com.cn{ server 192.168.10.1:80 ;…...

原型模式:复制对象的技巧

欢迎来到设计模式系列的第六篇文章&#xff01;在前面的几篇文章中&#xff0c;我们已经学习了一些常见的设计模式&#xff0c;今天我们将继续探讨另一个重要的设计模式——原型模式。 原型模式简介 原型模式是一种创建型设计模式&#xff0c;它主要用于复制对象。原型模式通…...

ClickHouse进阶(五):副本与分片-1-副本与分片

进入正文前&#xff0c;感谢宝子们订阅专题、点赞、评论、收藏&#xff01;关注IT贫道&#xff0c;获取高质量博客内容&#xff01; &#x1f3e1;个人主页&#xff1a;含各种IT体系技术,IT贫道_Apache Doris,大数据OLAP体系技术栈,Kerberos安全认证-CSDN博客 &#x1f4cc;订阅…...

Android 华为手机荣耀8X调用系统裁剪工具不能裁剪方形图片,裁剪后程序就奔溃,裁剪后获取不到bitmap的问题

买了个华为荣耀8X,安装自己写的App后,调用系统裁剪工具发现裁剪是圆形的,解决办法: //专门针对华为手机解决华为手机裁剪图片是圆形图片的问题 if (Build.MANUFACTURER.equals("HUAWEI")) {intent.putExtra("aspectX", 9998);intent.putExtra("a…...

《Flink学习笔记》——第十二章 Flink CEP

12.1 基本概念 12.1.1 CEP是什么 1.什么是CEP&#xff1f; 答&#xff1a;所谓 CEP&#xff0c;其实就是“复杂事件处理&#xff08;Complex Event Processing&#xff09;”的缩写&#xff1b;而 Flink CEP&#xff0c;就是 Flink 实现的一个用于复杂事件处理的库&#xff08…...

谷歌IndexedDB客户端存储数据

IndexedDB 具有以下主要特点&#xff1a; 1.存储大量数据&#xff1a;IndexedDB 可以存储大量的数据&#xff0c;比如存储离线应用程序的本地缓存或存储在线应用程序的大量数据。 2.结构化数据&#xff1a;IndexedDB 使用对象存储空间&#xff08;Object Stores&#xff09;来…...

天气数据的宝库:解锁天气预报API的无限可能性

前言 天气预报一直是我们日常生活中的重要组成部分。我们依赖天气预报来决定穿什么衣服、何时出行、规划户外活动以及做出关于农业、交通和能源管理等方面的重要决策。然而&#xff0c;要提供准确的天气预报&#xff0c;需要庞大的数据集和复杂的计算模型。这就是天气预报API的…...

插入排序(Insertion Sort)

C自学精简教程 目录(必读) 插入排序 每次选择未排序子数组中的第一个元素&#xff0c;从后往前&#xff0c;插入放到已排序子数组中&#xff0c;保持子数组有序。 打扑克牌&#xff0c;起牌。 输入数据 42 20 17 13 28 14 23 15 执行过程 完整代码 #include <iostream…...

2023蓝帽杯初赛

最近打完蓝帽杯 现在进行复盘 re 签到题 直接查看源代码 输出的内容就是 变量s 变量 number 而这都是已经设定好了的 所以flag就出来了 WhatisYourStory34982733 取证 案件介绍 取证案情介绍&#xff1a; 2021年5月&#xff0c;公安机关侦破了一起投资理财诈骗类案件&a…...

风险评估

风险评估概念 风险评估是一种系统性的方法&#xff0c;用于识别、评估和量化潜在的风险和威胁&#xff0c;以便组织或个人能够采取适当的措施来管理和减轻这些风险。 风险评估的目的 风险评估要素关系 技术评估和管理评估 风险评估分析原理 风险评估服务 风险评估实施流程...

直播软件app开发中的AI应用及前景展望

在当今数字化时代&#xff0c;直播市场蓬勃发展&#xff0c;而直播软件App成为人们获取实时信息和娱乐的重要渠道。随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;直播软件App开发正逐渐融入AI的应用&#xff0c;为用户带来更智能、更个性化的直播体验。 …...

vscode html使用less和快速获取标签less结构

扩展插件里面搜索 css tree 插件 下载 使用方法 选择你要生成的标签结构然后按CTRLshiftp 第一次需要在输入框输入 get 然后选择 Generate CSS tree less结构就出现在这个里面直接复制到自己的less文件里面就可以使用了 在html里面使用less 下载 Easy LESS 插件 自己创建…...

excel中的引用与查找函数篇1

1、COLUMN(reference)&#xff1a;返回与列号对应的数字 2、ROW(reference)&#xff1a;返回与行号对应的数字 参数reference表示引用/参考单元格&#xff0c;输入后引用单元格后colimn()和row()会返回这个单元格对应的列号和行号。若参数reference没有引用单元格&#xff0c;…...

【python】—— 函数详解

前言&#xff1a; 本期&#xff0c;我们将要讲解的是有关python中函数的相关知识&#xff01;&#xff01;&#xff01; 目录 &#xff08;一&#xff09;函数是什么 &#xff08;二&#xff09;语法格式 &#xff08;三&#xff09;函数参数 &#xff08;四&#xff09;函…...

springboot web开发登录拦截器

在SpringBoot中我们可以使用HandlerInterceptorAdapter这个适配器来实现自己的拦截器。这样就可以拦截所有的请求并做相应的处理。 应用场景 日志记录&#xff0c;可以记录请求信息的日志&#xff0c;以便进行信息监控、信息统计等。权限检查&#xff1a;如登陆检测&#xff…...

大数据课程K17——Spark的协同过滤法

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 了解Spark的协同过滤概念; 一、协同过滤概念 1. 概念 协同过滤是一种借助众包智慧的途径。它利用大量已有的用户偏好来估计用户对其未接触过的物品的喜好程度。其内在思想是相似度的定义…...

【力扣】1588. 所有奇数长度子数组的和 <前缀和>

【力扣】1588. 所有奇数长度子数组的和 给你一个正整数数组 arr &#xff0c;请你计算所有可能的奇数长度子数组的和。子数组 定义为原数组中的一个连续子序列。请你返回 arr 中 所有奇数长度子数组的和 。 示例 1&#xff1a; 输入&#xff1a;arr [1,4,2,5,3] 输出&#x…...

socket,tcp,http三者之间的原理和区别

目录 1、TCP/IP连接 2、HTTP连接 3、SOCKET原理 4、SOCKET连接与TCP/IP连接 5、Socket连接与HTTP连接 socket&#xff0c;tcp&#xff0c;http三者之间的区别和原理 http、TCP/IP协议与socket之间的区别 下面的图表试图显示不同的TCP/IP和其他的协议在最初OSI模型中的位置…...

19c补丁后oracle属主变化,导致不能识别磁盘组

补丁后服务器重启&#xff0c;数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后&#xff0c;存在与用户组权限相关的问题。具体表现为&#xff0c;Oracle 实例的运行用户&#xff08;oracle&#xff09;和集…...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查

在对接支付宝API的时候&#xff0c;遇到了一些问题&#xff0c;记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

376. Wiggle Subsequence

376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等

&#x1f50d; 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术&#xff0c;可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势&#xff0c;还能有效评价重大生态工程…...

工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配

AI3D视觉的工业赋能者 迁移科技成立于2017年&#xff0c;作为行业领先的3D工业相机及视觉系统供应商&#xff0c;累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成&#xff0c;通过稳定、易用、高回报的AI3D视觉系统&#xff0c;为汽车、新能源、金属制造等行…...

有限自动机到正规文法转换器v1.0

1 项目简介 这是一个功能强大的有限自动机&#xff08;Finite Automaton, FA&#xff09;到正规文法&#xff08;Regular Grammar&#xff09;转换器&#xff0c;它配备了一个直观且完整的图形用户界面&#xff0c;使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

python爬虫——气象数据爬取

一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用&#xff1a; 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests&#xff1a;发送 …...