Nginx 线程池

server/2024/9/19 19:24:40/ 标签: nginx, 数据库, linux

并发基本概念

并发:在同一时间段内,多个任务同时执行,偏向于多个任务交替执行,在某一时刻其实只有一个任务在执行(单个CPU就可并发,比如时间片轮转机制)。
并行:同一时刻,多个任务同时执行(并行需要有多个CPU)。

处理事件过程“阻塞”怎么办?
1.忙于漫长的 CPU 密集型处理;
2.读取文件,但文件尚未缓存,从硬盘中读取较为缓慢;
3.不得不等待获取某个资源:硬件驱动,网络上的请求和响应,互斥锁,等待同步方式调用的数据库响应等;

单个进程或线程同时只能处理一个任务,如果有很多请求需要同时处理怎么办?
运用多进程或多线程技术解决,但仍存在缺陷:
1.创建和销毁线程上花费的时间和消耗的系统资源,甚至可能要比花在处理实际的用户请求的时间和资源要多得多;
2.活动的线程需要消耗系统资源,如果启动太多,会导致系统由于过度消耗内存或“切换过度”而导致系统资源不足;

Nginx 线程池技术

线程池 - 由一个任务队列和一组处理队列的线程组成。一旦工作进程需要处理某个可能“阻塞”的操作,不用自己操作,将其作为一个任务放到线程池的队列,接着会被某个空闲线程提取处理。
在这里插入图片描述
在这里插入图片描述

任务:待处理的工作,通常由标识、上下文和处理函数;
任务队列:按顺序保存待处理的任务队列,等待线程中的线程组处理;
线程池:由多个已启动的一组线程组成;
条件变量:一种同步机制,允许线程挂起,直到共享数据上的某些条件得到满足;
互斥锁:保证在任一时刻,只能有一个线程访问对象;

ngx_thread_pool_t 结构体

struct ngx_thread_pool_s {ngx_thread_mutex_t        mtx;ngx_thread_pool_queue_t   queue;ngx_int_t                 waiting;ngx_thread_cond_t         cond;ngx_log_t                *log;ngx_str_t                 name;ngx_uint_t                threads;ngx_int_t                 max_queue;u_char                   *file;ngx_uint_t                line;
};
  1. mtx: 互斥锁,用于锁定任务队列,避免竞争状态。
  2. queue: 任务队列。
  3. waiting: 有多少个任务正在等待处理。
  4. cond: 用于通知线程池有任务需要处理。
  5. name: 线程池名称。
  6. threads: 线程池由多少个线程组成(线程数)。
  7. max_queue: 线程池最大能处理的任务数。

ngx_thread_task_t 结构体

struct thread_task_s {thread_task_t       *next;	uint_t               id;void                *ctx;	//上下文void               (*handler)(void *data);
};
  1. next: 指向下一个任务。
  2. id: 任务ID。
  3. ctx: 任务的上下文。
  4. handler: 处理任务的函数句柄。
  5. event: 跟任务关联的事件对象(当线程池处理成任务之后将会由主线程调用event对象的handler回调函数)。

线程池初始化

在 Nginx 启动的时候,首先会调用 ngx_thread_pool_init_worker() 函数来初始化线程池。ngx_thread_pool_init_worker() 函数最终会调用 ngx_thread_pool_init(),源码如下:

static ngx_int_t
ngx_thread_pool_init(ngx_thread_pool_t *tp, ngx_log_t *log, ngx_pool_t *pool)
{...for (n = 0; n < tp->threads; n++) {err = pthread_create(&tid, &attr, ngx_thread_pool_cycle, tp);if (err) {ngx_log_error(NGX_LOG_ALERT, log, err,"pthread_create() failed");return NGX_ERROR;}}...return NGX_OK;
}

ngx_thread_pool_init() 最终调用pthread_create()函数创建线程池中的工作线程,工作线程会从ngx_thread_pool_cycle()函数开始执行。

ngx_thread_pool_cycle()函数源码如下:

static void *
ngx_thread_pool_cycle(void *data)
{...for ( ;; ) {if (ngx_thread_mutex_lock(&tp->mtx, tp->log) != NGX_OK) {return NULL;}tp->waiting--;while (tp->queue.first == NULL) {if (ngx_thread_cond_wait(&tp->cond, &tp->mtx, tp->log) != NGX_OK){(void) ngx_thread_mutex_unlock(&tp->mtx, tp->log);return NULL;}}// 获取一个任务对象task = tp->queue.first;tp->queue.first = task->next;if (tp->queue.first == NULL) {tp->queue.last = &tp->queue.first;}if (ngx_thread_mutex_unlock(&tp->mtx, tp->log) != NGX_OK) {return NULL;}// 处理任务task->handler(task->ctx, tp->log);task->next = NULL;ngx_spinlock(&ngx_thread_pool_done_lock, 1, 2048);// 把处理完的任务放置到完成队列中*ngx_thread_pool_done.last = task;ngx_thread_pool_done.last = &task->next;ngx_unlock(&ngx_thread_pool_done_lock);(void) ngx_notify(ngx_thread_pool_handler); // 通知主线程}
}

ngx_thread_pool_cycle() 函数的主要工作是从待处理的任务队列中获取一个任务,然后调用任务对象的handler()函数处理任务,完成后把任务放置到完成队列中,并通过ngx_notify()通知主线程。

添加任务到任务队列

任务是主线程创建的(主线程负责处理客户端请求)
主线程通过ngx_thread_task_post()函数向任务队列中添加一个任务,代码如下:

ngx_int_t
ngx_thread_task_post(ngx_thread_pool_t *tp, ngx_thread_task_t *task)
{...if (ngx_thread_mutex_lock(&tp->mtx, tp->log) != NGX_OK) {return NGX_ERROR;}// 通知线程池有任务需要处理if (ngx_thread_cond_signal(&tp->cond, tp->log) != NGX_OK) {(void) ngx_thread_mutex_unlock(&tp->mtx, tp->log);return NGX_ERROR;}// 把任务添加到任务队列中*tp->queue.last = task;tp->queue.last = &task->next;tp->waiting++;(void) ngx_thread_mutex_unlock(&tp->mtx, tp->log);return NGX_OK;
}

ngx_thread_task_post() 函数首先调用 ngx_thread_cond_signal() 通知线程池的线程有任务需要处理,然后把任务添加到任务队列中。
先通知线程池在添加任务到任务队列中会不会有顺序问题?
其实这样做是没问题的,这是因为只要主线程不调用ngx_thread_mutex_unlock() 把互斥锁解开,线程池中的工作线程是不会从ngx_thread_cond_wait()返回的。

一文搞懂Nginx线程池机制原理

收尾工作

当线程池把任务处理完后会把其放置到完成队列中(ngx_thread_pool_done),然后调用ngx_notify()通知主线程有任务完成了。主线程收到通知后,会在事件模块中进行收尾工作:调用task.event.handler()task.event.handler由任务创建者设置。

Nginx 实现

封装互斥锁&条件变量

//thread.h
#ifndef _DEMO_THREAD_H_INCLUDED_
#define _DEMO_THREAD_H_INCLUDED_// 当C++程序调用这个代码的时候要声明下方的函数都以C语言方式编译
#ifdef __cplusplus
extern "C" {
#endif#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>typedef intptr_t        int_t;typedef uintptr_t       uint_t;#define  OK          0
#define  ERROR      -1// 对互斥量进行了封装  只能有一个线程拿到锁int thread_mutex_create(pthread_mutex_t *mtx);int thread_mutex_destroy(pthread_mutex_t *mtx);int thread_mutex_lock(pthread_mutex_t *mtx);int thread_mutex_unlock(pthread_mutex_t *mtx);// 对条件变量进行了封装  上锁后,阻塞等待信号发生,这样就可以触发任务int thread_cond_create(pthread_cond_t *cond);int thread_cond_destroy(pthread_cond_t *cond);int thread_cond_signal(pthread_cond_t *cond);int thread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mtx);#ifdef __cplusplus
}
#endif#endif /* _DEMO_THREAD_H_INCLUDED_ */
//thread_mutex.c
#include "thread.h"int
thread_mutex_create(pthread_mutex_t *mtx)
{int err;pthread_mutexattr_t  attr; // 互斥量属性err = pthread_mutexattr_init(&attr);if (err != 0) {// 向标准出错中输入fprintf(stderr, "pthread_mutexattr_init() failed, reason: %s\n", strerror(errno));return ERROR;}/*PTHREAD_MUTEX_ERRORCHECK:检测锁  就是防止死锁发生如果这个线程已经拿到锁了,然后还申请拿锁,如果不做处理就会照成死锁遇到这个情况设置PTHREAD_MUTEX_ERRORCHECK属性就会报错*/err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);if (err != 0) {fprintf(stderr, "pthread_mutexattr_settype(PTHREAD_MUTEX_ERRORCHECK) failed, reason: %s\n", strerror(errno));return ERROR;}// 使用 attr 初始化mtx锁  初始化后销毁 attrerr = pthread_mutex_init(mtx, &attr);if (err != 0) {fprintf(stderr, "pthread_mutex_init() failed, reason: %s\n", strerror(errno));return ERROR;}err = pthread_mutexattr_destroy(&attr);if (err != 0) {fprintf(stderr, "pthread_mutexattr_destroy() failed, reason: %s\n", strerror(errno));}return OK;
}// 销毁互斥量
int
thread_mutex_destroy(pthread_mutex_t *mtx)
{int  err;err = pthread_mutex_destroy(mtx);if (err != 0) {fprintf(stderr, "pthread_mutex_destroy() failed, reason: %s\n", strerror(errno));return ERROR;}return OK;
}// 上锁
int
thread_mutex_lock(pthread_mutex_t *mtx)
{int  err;err = pthread_mutex_lock(mtx);if (err == 0) {return OK;}fprintf(stderr, "pthread_mutex_lock() failed, reason: %s\n", strerror(errno));return ERROR;
}// 解锁
int
thread_mutex_unlock(pthread_mutex_t *mtx)
{int  err;err = pthread_mutex_unlock(mtx);#if 0ngx_time_update();
#endifif (err == 0) {return OK;}fprintf(stderr, "pthread_mutex_unlock() failed, reason: %s\n", strerror(errno));return ERROR;
}
//thread_cond.c
#include "thread.h"// 对条件变量进行了封装
int
thread_cond_create(pthread_cond_t *cond)
{int  err;// 参数:cond: 条件变量指针  attr:条件变量高级属性err = pthread_cond_init(cond, NULL);if (err == 0) {return OK;}fprintf(stderr, "pthread_cond_init() failed, reason: %s\n",strerror(errno));return ERROR;
}int
thread_cond_destroy(pthread_cond_t *cond)
{int  err;err = pthread_cond_destroy(cond);if (err == 0) {return OK;}fprintf(stderr, "pthread_cond_destroy() failed, reason: %s\n",strerror(errno));return ERROR;
}int
thread_cond_signal(pthread_cond_t *cond)
{int  err;err = pthread_cond_signal(cond);if (err == 0) {return OK;}fprintf(stderr, "pthread_cond_signal() failed, reason: %s\n",strerror(errno));return ERROR;
}int
thread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mtx)
{int  err;err = pthread_cond_wait(cond, mtx);if (err == 0) {return OK;}fprintf(stderr, "pthread_cond_wait() failed, reason: %s\n",strerror(errno));return ERROR;
}

线程池实现

//thread_pool.h
#ifndef _THREAD_POOL_H_INCLUDED_
#define _THREAD_POOL_H_INCLUDED_// 因为我们执行的是cpp程序,所以要告诉编译器下方的函数都以C语言方式编译
#ifdef __cplusplus
extern "C" {
#endif#include "thread.h"/* 
1.线程数太多,导致线程切换比较多,所以效率比较低,
但是如果阻塞的任务比较多,那么多开点线程,就会比较快
2.任务队列不可以无限多,因为每一个任务都占有内存,内存不可能无限多
*/
#define DEFAULT_THREADS_NUM 8    // 默认线程数8,因为我电脑核数为8
#define DEFAULT_QUEUE_NUM  65535 // 任务队列最大容量typedef unsigned long         atomic_uint_t;typedef struct thread_task_s  thread_task_t;// 线程任务typedef struct thread_pool_s  thread_pool_t;// 线程池// 任务结构体struct thread_task_s {thread_task_t       *next;  // 链表的下一个节点uint_t               id;    // 每一个任务都有一个id void                *ctx;   // 处理函数的参数void(*handler)(void *data); // 指向任务处理函数};typedef struct {thread_task_t        *first;thread_task_t        **last;// 指向最后一个节点,插入的时候直接使用这个} thread_pool_queue_t;// thread_pool_queue_init(q); 相当于:(q)->first = NULL;(q)->last = &(q)->first;
// 以后插入任务的时候只需要 last->next = 任务 , 不需要动用first
#define thread_pool_queue_init(q)                                         \(q)->first = NULL;                                                    \(q)->last = &(q)->firststruct thread_pool_s {pthread_mutex_t        mtx;   // 互斥锁thread_pool_queue_t   queue;  // 任务队列int_t                 waiting;// 没有处理的任务数pthread_cond_t         cond;  // 条件变量char                  *name;  // 线程池的名字uint_t                threads;// 线程池中线程数量int_t                 max_queue;// 队列的长度,队列中任务的容纳量};thread_task_t *thread_task_alloc(size_t size);// 给任务和处理任务的函数参数分配内存void thread_task_free(thread_task_t* task);// 释放内存int_t thread_task_post(thread_pool_t *tp, thread_task_t *task);// 把任务放入线程池thread_pool_t* thread_pool_init();// 对线程池初始化void thread_pool_destroy(thread_pool_t *tp);// 销毁线程池#ifdef __cplusplus
}
#endif#endif /* _THREAD_POOL_H_INCLUDED_ */
//thread_pool.cpp
#include "thread_pool.h"static void thread_pool_exit_handler(void *data);// 线程自杀
static void *thread_pool_cycle(void *data);//线程池的主循环
static int_t thread_pool_init_default(thread_pool_t *tpp, char *name);// 线程池默认参数static uint_t       thread_pool_task_id;
static int debug = 0;thread_pool_t* thread_pool_init()
{int             err;pthread_t       tid;uint_t          n;pthread_attr_t  attr;thread_pool_t   *tp=NULL;// 使用calloc初始化内存,初始内存会置零tp = (thread_pool_t*)calloc(1,sizeof(thread_pool_t));if(tp == NULL){fprintf(stderr, "thread_pool_init: calloc failed!\n");return NULL;}thread_pool_init_default(tp, NULL);// 初始化线程池thread_pool_queue_init(&tp->queue);// 会使用宏定义替换// 创建互斥锁和条件变量if (thread_mutex_create(&tp->mtx) != OK) {free(tp);return NULL;}if (thread_cond_create(&tp->cond) != OK) {(void) thread_mutex_destroy(&tp->mtx);free(tp);return NULL;}err = pthread_attr_init(&attr);// 给线程做初始化if (err) {fprintf(stderr, "pthread_attr_init() failed, reason: %s\n",strerror(errno));free(tp);return NULL;}/*PTHREAD_CREATE_DETACHED:意思就是和主线程断绝关系,主线程使用pthread_join 无法等待到结束的子进程*/err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);if (err) {fprintf(stderr, "pthread_attr_setdetachstate() failed, reason: %s\n",strerror(errno));free(tp);return NULL;}/*原型:int  pthread_create (pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void*),  void *arg);                                              参数:thread, 指向新线程的标识符。是一个传出参数attr, 用来设置新线程的属性。一般取默认属性,即该参数取NULLstart_routine, 该线程的处理函数该函数的返回类型和参数类型都是void*arg, 线程处理函数start_routine的参数*/for (n = 0; n < tp->threads; n++) {// 参数:tid , attr , 线程启动后执行函数 , thread_pool_cycle的参数// thread_pool_cycle:线程池的主循环err = pthread_create(&tid, &attr, thread_pool_cycle, tp);if (err) {fprintf(stderr, "pthread_create() failed, reason: %s\n",strerror(errno));free(tp);return NULL;}}(void) pthread_attr_destroy(&attr);// 销毁设置属性return tp;
}// 线程池的销毁
void thread_pool_destroy(thread_pool_t *tp)
{/* 我要干掉你这个线程就让你执行一个自杀函数就行了*/uint_t           n;thread_task_t    task;volatile uint_t  lock; // 无符号整形数memset(&task,'\0', sizeof(thread_task_t));task.handler = thread_pool_exit_handler;// 函数执行自杀task.ctx = (void *) &lock;// 自杀所有线程for (n = 0; n < tp->threads; n++) {lock = 1;// 向线程池中投递任务if (thread_task_post(tp, &task) != OK) {return;}while (lock) {sched_yield();// 线程放弃CPU的优先权}// 当自杀任务被执行完毕后,就会把这个任务从线程池中移除,详情参见thread_pool_cycle的源码实现//task.event.active = 0;}(void) thread_cond_destroy(&tp->cond);(void) thread_mutex_destroy(&tp->mtx);free(tp);
}// 函数执行自杀
static void
thread_pool_exit_handler(void *data)
{uint_t *lock = (uint_t *)data;*lock = 0;pthread_exit(0);
}// size:任务函数所要带的参数大小
thread_task_t *
thread_task_alloc(size_t size)
{thread_task_t  *task;// 一起分配内存:任务结构体 , 任务大小task = (thread_task_t  *)calloc(1,sizeof(thread_task_t) + size);if (task == NULL) {return NULL;}// 相当于task移动了sizeof(thread_task_t)个字节// 即被内存分成两份第一份放thread_task_t,第二份放ctxtask->ctx = task + 1;return task;
}void thread_task_free(thread_task_t * task)
{if (task) {free(task);task = NULL;}
}// 往线程池中投递任务
int_t
thread_task_post(thread_pool_t *tp, thread_task_t *task)
{// 上锁 独立占有线程池结构if (thread_mutex_lock(&tp->mtx) != OK) {return ERROR;}// 不可超过最大队列if (tp->waiting >= tp->max_queue) {// 解锁 ,打印队列已经满了(void) thread_mutex_unlock(&tp->mtx);fprintf(stderr,"thread pool \"%s\" queue overflow: %ld tasks waiting\n",tp->name, tp->waiting);return ERROR;}//task->event.active = 1;// thread_pool_task_id:是一个全局的静态变量task->id = thread_pool_task_id++;task->next = NULL;// 发送信号   唤醒条件变量锁if (thread_cond_signal(&tp->cond) != OK) {(void) thread_mutex_unlock(&tp->mtx);return ERROR;}// 向链表尾部插入任务*tp->queue.last = task;tp->queue.last = &task->next;// 等待任务数量 +1tp->waiting++;(void) thread_mutex_unlock(&tp->mtx);if(debug)fprintf(stderr,"task #%lu added to thread pool \"%s\"\n",task->id, tp->name);return OK;
}static void *
thread_pool_cycle(void *data)
{thread_pool_t *tp = (thread_pool_t *)data; // 拿到线程池结构体int                 err;thread_task_t       *task;if(debug)fprintf(stderr,"thread in pool \"%s\" started\n", tp->name);for ( ;; ) {// 上锁  自己独占资源,因为多线程一起访问的话很容易出错,比如多个线程对链表操作if (thread_mutex_lock(&tp->mtx) != OK) {return NULL;}// 上锁后,线程会拿到一个任务tp->waiting--;// 判断池子中有没有任务while (tp->queue.first == NULL) {//如果没有任务// thread_cond_wait :解锁-阻塞等待信号-信号来了-加锁-执行任务//当有任务来的时候,就会被唤醒  条件锁if (thread_cond_wait(&tp->cond, &tp->mtx)!= OK){// 函数执行错误就解锁(void) thread_mutex_unlock(&tp->mtx);return NULL;}}// 拿到队列中的任务后,把队列中这个任务去除即first指向下一个任务task = tp->queue.first;tp->queue.first = task->next;if (tp->queue.first == NULL) {tp->queue.last = &tp->queue.first;}//解锁if (thread_mutex_unlock(&tp->mtx) != OK) {return NULL;}if(debug) fprintf(stderr,"run task #%lu in thread pool \"%s\"\n",task->id, tp->name);task->handler(task->ctx);// 处理任务if(debug) fprintf(stderr,"complete task #%lu in thread pool \"%s\"\n",task->id, tp->name);task->next = NULL; //释放task// free(task); // 一次性把thread_task_t和处理任务的参数一起释放了thread_task_free(task);//notify }
}// 设置默认属性
static int_t
thread_pool_init_default(thread_pool_t *tpp, char *name)
{if(tpp){tpp->threads = DEFAULT_THREADS_NUM;// 设置线程数tpp->max_queue = DEFAULT_QUEUE_NUM;// 最大队列数tpp->name = strdup(name?name:"default");// 设置线程池名字if(debug)fprintf(stderr,"thread_pool_init, name: %s ,threads: %lu max_queue: %ld\n",tpp->name, tpp->threads, tpp->max_queue);return OK;}return ERROR;
}

nginx 线程池源码剖析

Nginx线程池剖析


http://www.ppmy.cn/server/38907.html

相关文章

Mac环境下ollama部署和体验

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码)&#xff1a;https://github.com/zq2599/blog_demos 关于ollama ollama和LLM&#xff08;大型语言模型&#xff09;的关系&#xff0c;类似于docker和镜像&#xff0c;可以在ollama服务中管理和运行各种LLM&…

多线程学习D10 收尾了应该

线程安全集合类概述 重点介绍java.util.concurrent.* 下的线程安全集合类&#xff0c;可以发现它们有规律&#xff0c;里面包含三类关键词&#xff1a;Blocking、CopyOnWrite、Concurrent Blocking 大部分实现基于锁&#xff0c;并提供用来阻塞的方法 CopyOnWrite 之类容器修改…

SpringBoot的ProblemDetails

1.RFC 7807 之前的项目如果出现异常&#xff0c;默认跳转到error页面。或者是抛出500 异常。 但是对于前后端分离的项目&#xff0c;Java程序员不负责页面跳转&#xff0c;只需要 把错误信息交给前端程序员处理即可。而RFC 7807规范就是将异常 信息转为JSON格式的数据。这个…

Spark SQL

一、简介 Spark SQL 是 Spark 用于结构化数据(structured data)处理的 Spark 模块。 1.特点: ➢ 数据兼容方面 SparkSQL 不但兼容 Hive&#xff0c;还可以从 RDD、parquet 文件、JSON 文件中获取数据&#xff0c;未来版本甚至支持获取 RDBMS 数据以及 cassandra 等 NOSQL 数据…

zookeeper之分布式环境搭建

ZooKeeper的分布式环境搭建是一个涉及多个步骤的过程&#xff0c;主要包括准备工作、安装ZooKeeper、配置集群、启动服务以及验证集群状态。以下是搭建ZooKeeper分布式环境的基本步骤&#xff1a; 1. 准备工作 确保所有节点的系统时间同步。确保所有节点之间网络互通&#xf…

java如何打印数组所有元素

java如何打印数组所有元素 用for循环的话 语法格式是 for(int i0;i<数组名.length;i) { System.out.prontln(数组名[i]); } 如果用while循环 先定义一个变量&#xff0c;变量的值等于0 假定变量名为j int j0; while(j<数组名.length) { System.out.println(数组…

PHPStudy 访问网页 403 Forbidden禁止访问

涉及靶场 upload-labd sqli-labs pikachu dvwa 以及所有部署在phpstudy中的靶场 注意&#xff1a;一定要安装解压软件 很多同学解压靶场代码以后访问报错的原因是&#xff1a;电脑上没有解压软件。 这个时候压缩包看起来就是黄色公文包的样子&#xff0c;右键只有“全部提取…

鸿蒙开发-ArkTS语言-容器-非线性容器

鸿蒙开发-UI-web 鸿蒙开发-UI-web-页面 鸿蒙开发-ArkTS语言-基础类库 鸿蒙开发-ArkTS语言-并发 鸿蒙开发-ArkTS语言-并发-案例 鸿蒙开发-ArkTS语言-容器 文章目录 前言 一、非线性容器 1.HashMap 2.HashSet 3.TreeMap 4.TreeSet 5.LightWeightMap 6.LightWeightSet 7.P…

leetcode300. 最长递增子序列

class Solution {public int lengthOfLIS(int[] nums) {//除了使用动态规划之外&#xff0c;还可以选择使用排序的方法。int[] maxLen new int[nums.length];maxLen[0] 1;for(int i 1;i < nums.length;i){int j i-1;int maxPre 0;for(;j > 0;j--)if(nums[j] < nu…

C++可变参数模板类通过递归和特化方式展开

可变参数模版类有2种方式展开参数包&#xff1a;通过继承和通过递归特化。在此只举例一个后着的例子以阐述展开的方式和过程。这些内容其实书上都有&#xff0c;我只是在看《深入C11 代码优化与工程应用》一书中遇到了些困惑&#xff0c;可能书中的写法与我的理解不对版&#x…

【C++ | 语句】条件语句(if、switch)、循环语句(while、do while、for、范围for)、跳转语句、try语句块和异常处理

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; ⏰发布时间⏰&#xff1a;2024-05-02 2…

2024年软件测试最全Jmeter--【作为测试你必须要知道的】基础名词与环境搭建,2024年最新年末阿里百度等大厂技术面试题汇总

网上学习资料一大堆&#xff0c;但如果学到的知识不成体系&#xff0c;遇到问题时只是浅尝辄止&#xff0c;不再深入研究&#xff0c;那么很难做到真正的技术提升。 需要这份系统化的资料的朋友&#xff0c;可以戳这里获取 一个人可以走的很快&#xff0c;但一群人才能走的更…

计算机网络实验——学习记录七(IP协议)

1. Linux下虚拟的以太网卡的MTU1500&#xff1b; 2. nping --udp -p4499 -g40321 -c1 --data-length 1400 192.168.57.254 &#xff08;1&#xff09;nping 命令创建指定大小的数据包&#xff1b; &#xff08;2&#xff09;--udp 指定nping命令创建的数据包是udp数据报&…

【12572物联网知识学习总结】

一、物联网的定义 物联网就是“物物相连的智能互联网”。它通过射频识别 &#xff08;RFID&#xff09;、红外感应器、全球定位系统、激光扫描器等信息传感 设备&#xff0c;按约定的协议&#xff0c;把任何物品与互联网连接起来&#xff0c;进行信息 交换和通讯&#xff0c;以…

<Linux> 权限

目录 权限人员相对于文件来说的分类更改权限文件的拥有者与所属组umask粘滞位 权限 权限是操作系统用来限制对资源访问的机制&#xff0c;权限一般分为读、写、执行。系统中的每个文件都拥有特定的权限、所属用户及所属组&#xff0c;通过这样的机制来限制哪些用户、哪些组可以…

python面向函数

组织好的&#xff0c;可重复利用的&#xff0c;用来实现单一&#xff0c;或相关联功能的代码段&#xff0c;避免重复造轮子&#xff0c;增加程序复用性。 定义方法为def 函数名 (参数) 参数可动态传参&#xff0c;即使用*args代表元组形式**kwargs代表字典形式&#xff0c;代替…

Redis 相关问题总结

Redis 相关问题 Redis 持久化机制 缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题 热点数据和冷数据是什么 Memcache与Redis的区别都有哪些&#xff1f; 单线程的redis为什么这么快 redis的数据类型&#xff0c;以及每种数据类型的使用场景&#xff0c;Redis 内…

android TV app适配遥控器思路,recycleview选中放大

背景&#xff1a; 1、当遥控器遥控盒子&#xff0c;app内是有一套机制&#xff0c;响应遥控器的操作; 2、要实现遥控器选中的效果&#xff0c;必须要设置setOnFocusChangeListener方法&#xff0c;另外一个就是设置view的setOnClickListener方法&#xff1b;设置完之后&#…

Kubernetes——基础认识

目录 前言 什么是云原生 云元素 K8s与中间件以及微服务之间的关系 Kubernetes发展历史 一、简介 1.Kubernetes是什么 2.为什么要使用Kubernetes 3.Kubernetes特性 3.1自我修复 3.2弹性伸缩 3.3自动部署和回滚 3.4服务发现和负载均衡 3.5集中化配置管理和密钥管理…

【前端】-【前端文件操作与文件上传】-【前端接受后端传输文件指南】

目录 前端文件操作与文件上传前端接受后端传输文件指南 前端文件操作与文件上传 一、前端文件上传有两种思路&#xff1a; 二进制blob传输&#xff1a;典型案例是formData传输&#xff0c;相当于用formData搭载二进制的blob传给后端base64传输&#xff1a;转为base64传输&…