linux--多线程(一)
文章目录
- Linux线程的概念
- 线程的优点
- 线程的缺点
- 线程异常
- 线程的控制
- 创建线程
- 线程ID以及进程地址空间
- 终止线程
- 线程等待
- 线程分离
- 线程互斥
- 进程线程间的互斥相关概念
- 互斥量mutex
- 有线程安全问题的售票系统
- 查看ticket--部分的汇编代码
- 互斥量的接口
- 互斥量实现原理探究
- 可重入和线程安全
- 常见的线程不安全的情况
- 常见的线程安全的情况
- 常见不可重入的情况
- 常见可重入的情况
- 可重入与线程安全的联系与区别
- 常见锁的概念
- 死锁
- 死锁的四个必要条件
Linux线程的概念
- 在一个程序里的一个执行路线就叫做线程(thread),更准确的定义是:线程是”一个进程内部的控制序列“
- 一切进程至少都有一个执行线程,线程在进程内部运行,本质是在进程地址空间内运行
- 在Linux系统中,在CPU眼里,看到的PCB要比传统的进程更加轻量化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KKOvTHlq-1677599958611)(D:\blogs\Linux中的执行流共享内存空间.png)]
线程的优点
- 创建一个新线程的代价要比创建一个新进程要小得多
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用:为了能在多处理系统上运行,将计算分解到多个线程中实现
- I/O密集型应用:为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作
线程的缺点
性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与其他线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
健壮性降低
编写多线程程序需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,也就是说线程 之间是缺乏保护的
缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响
线程异常
- 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
- 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也随即退出
线程的控制
与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以"pthread_“打头的。要使用这些函数库要通过引入头文件<pthread.h>,链接这些线程函数库时要使用编译器命令的”-lpthread"选项。
创建线程
/*
功能:创建一个新的线程
函数原型:int pthread_create(pthread_t* thread, const pthread_attr_t *arrt, void* (*call)(void*), void* arg);thread:返回线程IDattr:设置线程的属性,attr为NULL表示使用默认属性call:是一个函数指针,线程启动后要执行的函数arg:传给线程启动函数的参数
返回值:成功返回0,失败返回错误码
*/#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>void* call_back(void* arg)
{for(;;){printf("我是线程:%d&& %s\n",pthread_self(),(char*)arg);sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid,NULL,call_back,"hello word");while(1){printf("我是父线程:%d, 创建了子线程:%d\n",pthread_self(),tid);sleep(1);}return 0;
}
创建线程的运行结果
线程ID以及进程地址空间
pthread_create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事
前面说的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程
pthread_create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴,线程库的后续操作,就是根据该线程ID来操作线程的
NPTL线程库提供了pthread_self函数,可以获得线程自身的ID
pthread_t pthread_self(void);
pthread_t
类型的线程ID,本质就是一个进程地址空间上的一个地址
终止线程
终止一个线程而不终止整个进程,有三种方法:
从线程函数return,这种方法对主线程不适用,从main函数return相当于调用exit
线程可以调用pthread_exit终止自己
/* 功能:终止线程 原型 */ int pthread_exit(void* value_ptr); /*参数*/ // value_ptr:value_ptr不要指向一个局部变量,它指向的数据将作为线程退出时的返回值,如果线程不需要返回任何数据,将value_ptr参数置为NULL即可 // 无返回值,跟进程一样,线程借宿的时候无法返回到它的调用者// 举个例子 #include <unistd.h> #include <pthread.h> #include <stdio.h>void* func(void* args) {printf("欢迎光临\n");sleep(3);pthread_exit((void*)"tuichu"); }int main() {int ret = 0;void* str;pthread_t tid;ret = pthread_create(&tid,NULL,func,NULL);pthread_join(tid,&str);printf("%s\n",(char*)str);return 0; }
一个线程可以调用pthread_cancel终止同一进程中的另一个线程
/*功能取消一个线程*/ //原型void pthread_cancel(pthread_t tid); // 参数tid:线程ID // 返回值:成功返回0,失败返回错误码 // 举个例子 #include <unistd.h> #include <pthread.h> #include <stdio.h>void* func(void* args) {while(1){printf("我是线程%d\n",pthread_self()); sleep(1);} }int main() {pthread_t tid1, tid2;pthread_create(&tid1,NULL,func,NULL);pthread_create(&tid2,NULL,func,NULL);pthread_detach(tid1);pthread_detach(tid2);sleep(3);pthread_cancel(tid1);printf("取消线程%d\n",tid1);sleep(2);printf("取消线程%d\n",tid2);pthread_cancel(tid2);return 0; }
线程等待
已经退出的线程,其空间没有被释放,任然在进程的地址空间内。此时创建新的线程是不会复用刚才退出线程的地址空间的
//功能:等待线程结束
//原型int pthread_join(pthread_t tid, void** value_ptr);
// 参数
/*tid:线程IDvalue_ptr:它指向一个指针,后者指向线程的返回值返回值:成功返回0,失败返回错误码
*/
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下
- 如果thread线程通过return返回,
value_ptr
所指向的单元里存放的是thread线程函数的返回值。- 如果thread线程被别的线程调用
pthread_cancel
异常终止,value_ptr
所指向的单元里存放的是常数PTHREAD_CANCELD
- 如果thread线程是自己调用
pthread_exit
终止的,value_ptr
所指向的单元存放的是传给phtread_exit
的参数- 如果不考虑线程的终止状态,可以传NULL给
value_ptr
线程分离
- 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行
pthread_join
操作,否则无法释放资源,从而造成系统泄露 - 如果不关心线程的返回值,join是一种负担,因为主线程会阻塞在join这一步,这个时候我们可以告诉系统,当线程退出时,自动释放线程资源
// 可以主线程分离子线程
int pthread_detach(pthread_t tid);
// 也可以子线程自己分离自己
int pthread_detach(pthread_self);
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *thread_run( void * arg )
{pthread_detach(pthread_self());printf("%s\n", (char*)arg);return NULL;
}
int main( void )
{pthread_t tid;if ( pthread_create(&tid, NULL, thread_run, "thread1 run...") != 0 ) {printf("create thread error\n");return 1;}int ret = 0;sleep(1);//很重要,要让线程先分离,再等待if ( pthread_join(tid, NULL ) == 0 ) {printf("pthread wait success\n");ret = 0;} else {printf("pthread wait failed\n");ret = 1;}return ret;
}
线程互斥
进程线程间的互斥相关概念
- 临界资源:多线程执行流共享的资源就叫做临界资源
- 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
- 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
- 原子性:不会被任何调度机制打断的操作,该操作只有两种状态,要么完成,要么未完成
互斥量mutex
- 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量
- 很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互
有线程安全问题的售票系统
// 操作共享变量会有问题的售票系统代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
// 定义100张票
int ticket = 100;// 线程的回调函数
void* route(void* arg)
{char* id = (char*)arg;while(1){if(ticket > 0){usleep(1000);printf("%s sells ticket:%d\n", id, ticket);ticket--; // 剩余票数减一}else{break;}}
}int main()
{// 创建4个线程模拟买票操作pthread_t t1, t2, t3, t4;pthread_create(&t1, NULL, route, "thread 1");pthread_create(&t2, NULL, route, "thread 2");pthread_create(&t3, NULL, route, "thread 3");pthread_create(&t4, NULL, route, "thread 4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);return 0;
}
查看执行结果我们发现售卖的票为0的时候还在往外售出,这肯定是存在问题,那么是为什么会导致这样的情况出现呢?
if
语句判断条件为真以后,代码可以并发的切换到其他线程usleep
这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段--ticket
操作本身就不是一个原子操作
查看ticket–部分的汇编代码
使用
objdump -d ticketing_system > test.objdump
指令获取汇编代码[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NAH0hGOq-1677599958615)(D:\blogs\ticket–的汇编代码.png)]
可以看到
--
操作是由很多步构成的,并不是原子的
load
:将共享变量ticket从内存加载到寄存器中update
:更新寄存器里面的值,执行-1操作store
:将新值,从寄存器写回共享变量ticket的内存地址要解决以上问题,需要做到三点
- 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区
- 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区
- 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区
要做到以上三点,本质上就是需要一把锁,Linux上提供的这把锁叫互斥量。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AKlm0Ls3-1677599958615)(D:\blogs\互斥量.png)]
互斥量的接口
初始化互斥量
初始化互斥量有两种方法:
- 方法1:静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
- 方法2:动态分配
int pthread_mutex_init(pthread_mutex_t* restrict mutex, const pthread_mutexattr_t* restrict attr);
/*
参数:mutex:要初始化的互斥量attr:NULL
*/
销毁互斥量
销毁互斥量需要注意:
- 使用
PTHREAD_MUTEX_INITIALIZER
初始化的互斥量不需要销毁- 不要销毁一个已经加锁的互斥量
- 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
int pthread_mutex_destroy(pthread_mutex_t* mutex);
互斥量加锁和解锁
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);
返回值:成功返回0,失败返回错误号
调用pthread_lock
时,可能回遇到以下情况:
- 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
- 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么
pthread_lock
调用回陷入阻塞,等待互斥量解锁
改进后的售票系统
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>int ticket = 100;
// 定义互斥量
pthread_mutex_t mutex;
void *route(void *arg)
{char *id = (char *)arg;// 加锁while (1){pthread_mutex_lock(&mutex);if (ticket > 0){usleep(1000);printf("%s sells ticket:%d\n", id, ticket);ticket--;// 解锁pthread_mutex_unlock(&mutex);}else{// 如果没有票了也要释放锁pthread_mutex_unlock(&mutex);break;}}
}int main()
{pthread_t t1, t2, t3, t4;// 初始化锁pthread_mutex_init(&mutex, NULL);pthread_create(&t1, NULL, route, "thread 1");pthread_create(&t2, NULL, route, "thread 2");pthread_create(&t3, NULL, route, "thread 3");pthread_create(&t4, NULL, route, "thread 4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);// 销毁锁pthread_mutex_destroy(&mutex);return 0;
}
互斥量实现原理探究
- 经过上面的例子,大家已经意识到单纯的
++i
或者i++
都不是原子的,有可能会有数据一致性问题- 为了实现互斥锁操作,大多数体系结构都提供了
swap
或者exchange
指令,该指令的作用是把寄存器喝内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的总线周期也有先后,一个处理上的交换指令执行时另一个处理器的交换指令只能等待总线周期
把lock和unlock的伪代码改一下
lock:movb $0, %al xchgb %al, mutex // 原子操作,直接将al的值和mutex交换if(al寄存器的内容 > 0) // 竞争到锁{return 0;}else // 没竞争到锁挂起等待;goto lock;
unlock:movb $1, mutex唤醒等待mutex的线程;return 0;
可重入和线程安全
- 线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现线程安全问题。
- 重入:同一个函数被不同的执行流调用,当一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入情况下,运行结果不会出现任何不同或者任何问题,则函数被称为可重入函数,否则,是不可重入函数。
常见的线程不安全的情况
- 不保护共享变量的函数
- 函数状态随着被调用,状态发生变化的函数
- 返回指向静态变量指针的函数
- 调用线程不安全函数的函数
常见的线程安全的情况
- 每个线程对全局变量或者静态变量只有读取权限,而没有写入权限,一般来说这些线程是安全的
- 类或接口对于线程来说都是原子操作
- 多个线程之间的切换不会导致该接口的执行结果存在二义性
常见不可重入的情况
- 调用了
malloc/free
函数,因为malloc函数是用全局链表来管理堆的- 调用标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
- 可重入函数体内使用了静态的数据结构
常见可重入的情况
- 不使用全局变量或静态变量
- 不使用用
malloc
或new
开辟出的空间- 不调用不可重入函数
- 不返回静态或全局数据,所有数据都由函数的调用者提供
- 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据
可重入与线程安全的联系与区别
联系
- 函数是可重入的,那就是线程安全的
- 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
- 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的
区别
- 可重入函数是线程安全函数的一种
- 线程安全不一定是可重入的,而可重入函数一定是线程安全的
- 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的
常见锁的概念
死锁
死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于一种永久等待状态
死锁的四个必要条件
- 互斥条件:一个资源每次只能被一个执行流使用
- 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:一个执行流已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系
相关文章:

linux--多线程(一)
文章目录Linux线程的概念线程的优点线程的缺点线程异常线程的控制创建线程线程ID以及进程地址空间终止线程线程等待线程分离线程互斥进程线程间的互斥相关概念互斥量mutex有线程安全问题的售票系统查看ticket--部分的汇编代码互斥量的接口互斥量实现原理探究可重入和线程安全常…...

计算机组成原理(2.1)--系统总线
目录 一、总线基本知识 1.总线 2.总线的信息传送 3.分散连接图 4.注 二、总线结构的计算机举例 1.面向 CPU 的双总线结构框图 2.单总线结构框图 3.以存储器为中心的双总线结构框图 三、总线的分类 1.片内总线 2.系统总线 (板级总线或板间总线&#…...

C语言数组【详解】
数组1. 一维数组的创建和初始化1.1 数组的创建1.2 数组的初始化1.3 一维数组的使用1.4 一维数组在内存中的存储2. 二维数组的创建和初始化2.1 二维数组的创建2.2 二维数组的初始化2.3 二维数组的使用2.4 二维数组在内存中的存储3. 数组越界4. 数组作为函数参数4.1 冒泡排序函数…...
并行与体系结构会议
A类会议 USENIX ATC 2022: USENIX Annual Technical Conference(录用率21%) CCF a, CORE a, QUALIS a1 会议截稿日期:2022-01-06 会议通知日期:2022-04-29 会议日期:2022-07-11 会议地点:Carlsbad, Califo…...

【巨人的肩膀】JAVA面试总结(三)
1、💪 目录1、💪1、说说List, Set, Queue, Map 四者的区别1.1、List1.2、Set1.3、Map2、如何选用集合4、线程安全的集合有哪些?线程不安全的呢?3、为什么需要使用集合4、comparable和Comparator的区别5、无序性和不可重复性的含义…...

嵌入式 STM32 SHT31温湿度传感器
目录 简介 1、原理图 2、时序说明 数据传输 起始信号 结束信号 3、SHT31读写数据 SHT31指令集 读数据 温湿度转换 4、温湿度转换应用 sht3x初始化 读取温湿度 简介 什么是SHT31? 一主机多从机--通过寻址的方式--每个从机都有唯一的地址&…...

哪款蓝牙耳机打电话好用?打电话音质好的蓝牙耳机
现在几乎是人人离不开耳机的时代。在快节奏的生活和充满嘈杂声音的世界中,戴着耳机听歌,是每个人生活中最不可或缺的一段自由、放松的时光,下面小编就来分享几款通话音质好的蓝牙耳机。 一、南卡小音舱蓝牙耳机 动圈单元:13.3mm…...
【C++】-- 内存泄漏
目录 内存泄漏 内存泄漏分类 如何检测内存泄漏 如何避免内存泄漏 内存泄漏 #问:什么是内存泄漏?内存泄漏:指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某…...

C++ STL学习之【string类的模拟实现】
✨个人主页: Yohifo 🎉所属专栏: C修行之路 🎊每篇一句: 图片来源 The key is to keep company only with people who uplift you, whose presence calls forth your best. 关键是只与那些提升你的人在一起,…...

Selenium基于POM的自动化测试实践
什么是Page Object模式 Page Object 见名知意,就是页面对象,并将页面元素定位方法和元素操作进行分离。在实际自动化测试实战过程中,我们一般对脚本的实现分为三层: (1)对象层: 用于存放页面元素定位和控件操作 (2)逻…...

记录每日LeetCode 2373.矩阵中的局部最大值 Java实现
题目描述: 给你一个大小为 n x n 的整数矩阵 grid 。 生成一个大小为 (n - 2) x (n - 2) 的整数矩阵 maxLocal ,并满足: maxLocal[i][j] 等于 grid 中以 i 1 行和 j 1 列为中心的 3 x 3 矩阵中的 最大值 。 换句话说,我们希…...

QT中级(6)基于QT的文件传输工具(2)
QT中级(6)基于QT的文件传输工具(2)本文实现第一步1 新增功能2 运行效果3 实现思路4 源代码实现这个文件传输工具大概需要那几步?实现多线程对文件的读写实现TCP客户端和服务端实现网络传输 书接上回:QT中级…...

【Linux】工具(3)——gcc/g++
咱们继续进阶,接下来进入到Linux工具中gcc和g的学习在本章博客正式开始介绍之前,我们先要弄清楚程序是怎么翻译的:C语言程序环境一、什么是gcc/g📌gcc是一个c编译器, g是c编译器。我们根据代码的后缀名来判断用哪个编译…...
Android文件选择器
使用方法:在里层的build.grade的dependency里面加入: implementation com.leon:lfilepickerlibrary:1.8.0 引用https://github.com/leonHua/LFilePicker/blob/master/README_CH.md#lfilepicker LFilePicker 说明:如果发现应用名称被修改,可以参考issues#26 查看解决方案,或…...

《MySql学习》 Select 查询语句慢的非性能原因
一.查询被阻塞 A会话执行 查询操作,长时间没有返回信息,此时我们就可以去排查一下是否是被阻塞了 select * from words 被阻塞的原因有很多,首先列举第一种情况 1.等MDL锁 当我们执行DDL语句时,会自动给表加上MDL写锁。当执行DML和DQL时&…...

Vue组件间通信方式超详细(父传子、父传后代、子传父、后代传父、兄弟组件传值)
一、父传子、父传后代 方式一:子通过props来接收 父组件:父组件引入子组件时,通过<child :parentValue "parentValue"></child>子组件传值。 备注:这种方式父传值很方便,但是传递给后代组件不…...

【ES】Elasticsearch-深入理解索引原理
文章目录Elasticsearch-深入理解索引原理读操作更新操作SHARD不变性动态更新索引删除和更新实时索引更新持久化Segment合并近实时搜索,段数据刷新,数据可见性更新和事务日志更新索引并且将改动提交修改Searcher对象默认的更新时间Elasticsearch-深入理解…...

pdf压缩文件大小的方法是什么?word文件怎么批量转换成pdf格式?
大家在存储文件时,通常会遇到一些较大的文件,这时需要对其进行压缩处理。下面介绍一下如何压缩PDF文件大小以及批量转换Word文件为PDF格式。pdf压缩文件大小的方法是什么?1.打开小圆象PDF转换器,选择“PDF压缩”功能。2.在“PDF压缩”界面中…...

论文阅读——FECANet:应用特征增强的上下文感知小样本语义分割网络
代码:NUST-Machine-Intelligence-Laboratory/FECANET (github.com) 文章地址:地址 文章名称:FECANet: Boosting Few-Shot Semantic Segmentation with Feature-Enhanced Context-Aware Network 摘要 Few-shot semantic segmentation 是学习…...

数组模拟常见数据结构
我们来学习一下用数组模拟常见的数据结构:单链表,双链表,栈,队列。用数组模拟这些常见的数据结构,需要我们对这些数据结构有一定的了解哈。单链表请参考:http://t.csdn.cn/SUv8F 用数组模拟实现比STL要快&a…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
全面解析各类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? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...

Windows安装Miniconda
一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)
macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 🍺 最新版brew安装慢到怀疑人生?别怕,教你轻松起飞! 最近Homebrew更新至最新版,每次执行 brew 命令时都会自动从官方地址 https://formulae.…...
适应性Java用于现代 API:REST、GraphQL 和事件驱动
在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...
【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验
Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...

FFmpeg avformat_open_input函数分析
函数内部的总体流程如下: avformat_open_input 精简后的代码如下: int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options) {AVFormatContext *s *ps;int i, ret 0;AVDictio…...