【Linux】27.Linux 多线程(1)

文章目录

  • 1. Linux线程概念
    • 1.1 线程和进程
    • 1.2 虚拟地址是如何转换到物理地址的
    • 1.3 线程的优点
    • 1.4 线程的缺点
    • 1.5 线程异常
    • 1.6 线程用途
  • 2. Linux进程VS线程
    • 2.1 进程和线程
    • 2.2 关于进程线程的问题
  • 3. Linux线程控制
    • 3.1 POSIX线程库
    • 3.2 创建线程
    • 3.3 线程终止
    • 3.4 线程等待
    • 3.5 分离线程
  • 4. 线程ID及进程地址空间布局


1. Linux线程概念

1.1 线程和进程

  • 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”

  • 一切进程至少都有一个执行线程

  • 线程在进程内部运行,本质是在进程地址空间内运行

  • 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化

  • 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流

  • 线程是进程内的一个执行分支。线程的执行粒度要比进程要细

线程在进程的地址空间内运行。

为什么?

  1. 在Linux中,线程在进程“内部”执行,因为任何执行流要执行,都要有资源。地址空间是进程的资源窗口

  2. 在Linux中,线程的执行粒度要比进程要更细,因为线程执行进程代码的一部分

如何理解我们以前的进程?

操作系统以进程为单位,给我们分配资源,我们当前的进程内部,只有一个执行流。

重新定义线程和进程:

什么叫做线程?我们认为,线程操作系统调度的基本单位。

重新理解进程,内核观点:进程是承担分配系统资源的基本实体。

线程是我进程内部的执行流资源,一个进程有多个线程。

执行流也是资源。

Windows设计者是专门为了线程设计了一套和进程差不多的结构体struct tcb,用来管理线程。

不过,Linux设计觉得,进程和线程差不多,只是线程的颗粒度小一点,直接套用进程的那一套不就行了吗?

所以,Linux有线程,但是没有真正意义上的线程。

而是用“进程”(内核数据结构)模拟的线程。(这是一个卓越的想法)

Linux中的执行流也叫做:轻量级进程

线程 <= 执行流 <= 进程

ea6abfc35eb9b2c34a087dc2b49ec6f4


1.2 虚拟地址是如何转换到物理地址的

263ea0122087400b51b74814f6cfd764


1.3 线程的优点

  • 创建一个新线程的代价要比创建一个新进程小得多

  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多

  • 线程占用的资源要比进程少很多

  • 能充分利用多处理器的可并行数量

  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务

  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现

  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。


1.4 线程的缺点

  • 性能损失

    • 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
  • 健壮性降低

    • 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
  • 缺乏访问控制

    • 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
  • 编程难度提高

    • 编写与调试一个多线程程序比单线程程序困难得多

1.5 线程异常

  • 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃

  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出


1.6 线程用途

  • 合理的使用多线程,能提高CPU密集型程序的执行效率

  • 合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)


2. Linux进程VS线程

2.1 进程和线程

  • 进程是资源分配的基本单位

  • 线程是调度的基本单位

  • 线程共享进程数据,但也拥有自己的一部分数据:

    • 线程ID

    • 一组寄存器

    • errno

    • 信号屏蔽字

    • 调度优先级

进程的多个线程共享同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:

  • 文件描述符表
  • 每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
  • 当前工作目录
  • 用户id和组id

进程和线程的关系如下图:

0432af2f3ee71707c2673b167cdecfed


2.2 关于进程线程的问题

如何看待之前学习的单进程?

就是具有一个线程执行流的进程。


3. Linux线程控制

3.1 POSIX线程库

  • pthread 是 “POSIX threads” 的简写。POSIX线程库是官方完整名称,pthread就是它的常用简称。
  • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
  • 要使用这些函数库,要通过引入头文<pthread.h>
  • 链接这些线程函数库时要使用编译器命令的“-lpthread”选项

Linux内核本身并不直接支持线程这个概念,它只认识一种叫"轻量级进程"的东西。这就像内核只提供了基础的零件,而不是完整的工具。它不会给我直接提供线程的系统调用,只会给我们提供轻量级进程的系统调用。

但是我们日常编程时需要用到线程,这时候就需要一个"翻译官"- pthread线程库来帮忙。这个库就像是一个包装器,它把我们需要的线程操作转换成内核能理解的轻量级进程操作。

好消息是,几乎所有的Linux系统都默认安装了这个pthread库。所以当我们在Linux下要写多线程程序时,需要调用这个pthread库提供的接口,而不是直接使用内核的接口。

pthread线程库 – 应用层 – 轻量级进程接口进行封装。为用户提供直接线程的接口

Linux中编写多线程代码需要使用第三方pthread库。


3.2 创建线程

功能:创建一个新的线程
原型int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
参数thread:返回线程IDattr:设置线程的属性,attr为NULL表示使用默认属性start_routine:是个函数地址,线程启动后要执行的函数arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码

错误检查:

  • 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
  • pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通过返回值返回
  • pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>// 线程执行函数
void *rout(void *arg) {int i;// 无限循环for( ; ; ) {printf("I'am thread 1\n"); // 打印线程1的标识信息sleep(1);  // 休眠1秒,降低CPU占用} 
}int main( void )
{pthread_t tid;  // 用于保存线程IDint ret;        // 保存pthread_create的返回值// 创建新线程// pthread_create参数:// 1. &tid: 存储新创建线程ID的地址// 2. NULL: 线程属性,NULL表示使用默认属性// 3. rout: 线程将要执行的函数// 4. NULL: 传递给线程函数的参数,这里没有参数传递if ( (ret=pthread_create(&tid, NULL, rout, NULL)) != 0 ) {// 如果线程创建失败:// strerror将错误码转换为错误信息字符串fprintf(stderr, "pthread_create : %s\n", strerror(ret));// 退出程序,返回失败状态exit(EXIT_FAILURE);}int i;// 主线程的无限循环for(; ; ) {printf("I'am main thread\n"); // 打印主线程的标识信息sleep(1);  // 休眠1秒,降低CPU占用}
}

运行结果:

ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson36$ ./program
I'am main thread
I'am thread 1
I'am main thread
I'am thread 1
I'am main thread
I'am thread 1
I'am main thread
I'am thread 1
^C
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson36$ 
#include <pthread.h>
// 获取线程ID
pthread_t pthread_self(void);

打印出来的 tid 是通过 pthread 库中有函数 pthread_self 得到的,它返回一个 pthread_t 类型的变量,指代的是调用 pthread_self 函数的线程的 “ID”。

怎么理解这个“ID”呢?这个“ID”是 pthread 库给每个线程定义的进程内唯一标识,是 pthread 库维持的。

由于每个进程有自己独立的内存空间,故此“ID”的作用域是进程级而非系统级(内核不认识)。

其实 pthread 库也是通过内核提供的系统调用(例如clone)来创建线程的,而内核会为每个线程创建系统全局唯一的“ID”来唯一标识这个线程。

使用PS命令查看线程信息

运行代码后执行:

ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson36$ ps -aL | head -1 && ps -aL | grep programPID     LWP TTY          TIME CMD302889  302889 pts/1    00:00:00 program302889  302890 pts/1    00:00:00 program
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson36$ 

LWP 是什么呢?LWP 得到的是真正的线程ID。之前使用 pthread_self 得到的这个数实际上是一个地址,在虚拟地址空间上的一个地址,通过这个地址,可以找到关于这个线程的基本信息,包括线程ID,线程栈,寄存器等属性。

ps -aL 得到的线程ID,有一个线程ID和进程ID相同,这个线程就是主线程,主线程的栈在虚拟地址空间的栈上,而其他线程的栈在是在共享区(堆栈之间),因为pthread系列函数都是pthread库提供给我们的。而pthread库是在共享区的。所以除了主线程之外的其他线程的栈都在共享区。


3.3 线程终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

  1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit
  2. 线程可以调用pthread_exit终止自己。
  3. 一个线程可以调用pthread_cancel终止同一进程中的另一个线程。

pthread_exit函数

功能:线程终止
原型:void pthread_exit(void *value_ptr);
参数:value_ptr:void *value_ptr 是线程的返回值,value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

pthread_cancel函数

功能:取消一个执行中的线程
原型:int pthread_cancel(pthread_t thread);
参数:thread:线程ID
返回值:成功返回0;失败返回错误码

3.4 线程等待

为什么需要线程等待?

  • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
  • 创建新的线程不会复用刚才退出线程的地址空间。
功能:等待线程结束
原型int pthread_join(pthread_t thread, void **value_ptr);
参数:thread:线程IDvalue_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码

调用该函数的线程将挂起等待,直到idthread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

  1. 如果thread线程通过return返回,value_ptr所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_cancel异常终掉,value_ptr所指向的单元里存放的是常数PTHREAD_CANCELED
  3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
  4. 如果对thread线程的终止状态不感兴趣,可以传NULLvalue_ptr参数。

代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>// 线程1函数 - 通过return返回值
void *thread1( void *arg )
{printf("thread 1 returning ... \n");int *p = (int*)malloc(sizeof(int)); // 分配内存保存返回值*p = 1;  // 设置返回值为1return (void*)p;  // 返回指针
}// 线程2函数 - 通过pthread_exit返回值 
void *thread2( void *arg )
{printf("thread 2 exiting ...\n");int *p = (int*)malloc(sizeof(int)); // 分配内存保存返回值*p = 2;  // 设置返回值为2pthread_exit((void*)p);  // 使用pthread_exit退出并返回指针
}// 线程3函数 - 无限循环直到被其他线程取消
void *thread3( void *arg )
{while ( 1 ){ // 无限循环printf("thread 3 is running ...\n");sleep(1);  // 休眠1秒}return NULL;
}int main( void )
{pthread_t tid;  // 线程IDvoid *ret;      // 用于存储线程返回值的指针// 创建并等待线程1完成pthread_create(&tid, NULL, thread1, NULL);  // 创建线程1pthread_join(tid, &ret);  // 等待线程1结束并获取返回值printf("thread return, thread id %X, return code:%d\n", tid, *(int*)ret);free(ret);  // 释放返回值的内存// 创建并等待线程2完成pthread_create(&tid, NULL, thread2, NULL);  // 创建线程2pthread_join(tid, &ret);  // 等待线程2结束并获取返回值printf("thread return, thread id %X, return code:%d\n", tid, *(int*)ret);free(ret);  // 释放返回值的内存// 创建线程3并在3秒后取消它pthread_create(&tid, NULL, thread3, NULL);  // 创建线程3sleep(3);  // 主线程休眠3秒pthread_cancel(tid);  // 取消线程3pthread_join(tid, &ret);  // 等待线程3结束并获取返回状态if ( ret == PTHREAD_CANCELED )printf("thread return, thread id %X, return code:PTHREAD_CANCELED\n", tid);elseprintf("thread return, thread id %X, return code:NULL\n", tid);
}

运行结果:

ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson36$ ./program
thread 1 returning ... 
thread return, thread id 15C2A700, return code:1
thread 2 exiting ...
thread return, thread id 15C2A700, return code:2
thread 3 is running ...
thread 3 is running ...
thread 3 is running ...
thread return, thread id 15C2A700, return code:PTHREAD_CANCELED
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson36$ 

33becf3e3353c9807ef42a6d944a3b21


3.5 分离线程

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  • 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
int pthread_detach(pthread_t thread);
参数:pthread_t threadthread是要分离的线程IDpthread_t是线程ID的类型

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:

// 两种使用方式
pthread_t tid;// 1. 分离其他线程
pthread_create(&tid, NULL, thread_func, NULL);
pthread_detach(tid);// 2. 线程分离自己
void* thread_func(void* arg) {pthread_detach(pthread_self());  // pthread_self()获取当前线程IDreturn NULL;
}

joinable和分离是冲突的,一个线程不能既是joinable又是分离的。

// joinable状态(默认)
pthread_t tid1;
pthread_create(&tid1, NULL, func, NULL);
// - 必须被其他线程join
// - 资源需要手动回收
// - 可以获取线程返回值// detached状态
pthread_t tid2;
pthread_create(&tid2, NULL, func, NULL);
pthread_detach(tid2);
// - 结束时自动回收资源
// - 不能被join
// - 无法获取返回值

代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>// 线程执行函数
void *thread_run( void * arg )
{// pthread_detach用于分离线程,使线程结束时自动回收资源// pthread_self()获取当前线程的线程IDpthread_detach(pthread_self()); // 打印传入的参数字符串printf("%s\n", (char*)arg);return NULL;
}int main( void )
{pthread_t tid; // 用于保存线程ID// pthread_create创建新线程// 参数1:指向线程ID的指针// 参数2:线程属性,NULL表示使用默认属性// 参数3:线程执行的函数// 参数4:传递给线程函数的参数if ( pthread_create(&tid, NULL, thread_run, "thread1 run...") != 0 ) {printf("create thread error\n");return 1;}int ret = 0;// sleep(1)很重要// 这里等待1秒是为了让新创建的线程有机会执行pthread_detach()// 如果主线程不等待,可能会在线程detach之前就执行pthread_joinsleep(1);// pthread_join尝试等待tid指定的线程结束// 因为线程已经分离(detach),所以pthread_join会失败// 参数1:要等待的线程ID// 参数2:用于存储线程返回值的指针,这里为NULL表示不关心返回值if ( pthread_join(tid, NULL ) == 0 ) {printf("pthread wait success\n");ret = 0;} else {// 因为线程已分离,所以这里会执行,打印等待失败printf("pthread wait failed\n");ret = 1;}return ret;
}

4. 线程ID及进程地址空间布局

  • pthread_create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
  • 前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
  • pthread_create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
  • 线程库NPTL提供了pthread_self函数,可以获得线程自身的ID
pthread_t pthread_self(void);
返回值:pthread_t返回调用线程的线程IDpthread_t是线程标识符类型
参数:void无参数只能获取当前调用线程的ID

pthread_t 到底是什么类型呢?取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。

d9e3a501d0b0a1081ce2b7f978ca7028

2145960564e271a263fa7896e1ee010b

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/70299.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringAI系列 - 使用LangGPT编写高质量的Prompt

目录 一、LangGPT —— 人人都可编写高质量 Prompt二、快速上手2.1 诗人 三、Role 模板3.1 Role 模板3.2 Role 模板使用步骤3.3 更多例子 四、高级用法4.1 变量4.2 命令4.3 Reminder4.4 条件语句4.5 Json or Yaml 方便程序开发 一、LangGPT —— 人人都可编写高质量 Prompt La…

2025.2.6

一、C思维导图&#xff1a; 二、C&#xff1a; 三、注释代码 1> 配置文件&#xff1a;.pro文件 QT core gui # 引入的类库&#xff0c;core表示核心库 gui图形化界面库greaterThan(QT_MAJOR_VERSION, 4): QT widgets # 超过版本4的qt&#xff0c;会自动加widgets…

【算法应用】Alpha进化算法求解二维栅格路径规划问题

目录 1.算法原理2.二维路径规划数学模型3.结果展示4.参考文献5.代码获取 1.算法原理 Alpha进化&#xff1a;一种具有进化路径自适应和矩阵生成的高效进化算法 2.二维路径规划数学模型 栅格法模型最早由 W.E. Howden 于 1968 年提出&#xff0c;障碍物的栅格用黑色表示&#…

ubuntu20.04+RTX4060Ti大模型环境安装

装显卡驱动 这里是重点&#xff0c;因为我是跑深度学习的&#xff0c;要用CUDA&#xff0c;所以必须得装官方的驱动&#xff0c;Ubuntu的附件驱动可能不太行. 进入官网https://www.nvidia.cn/geforce/drivers/&#xff0c;选择类型&#xff0c;最新版本下载。 挨个运行&#…

Spring Boot 2 快速教程:WebFlux优缺点及性能分析(四)

WebFlux优缺点 【来源DeepSeek】 Spring WebFlux 是 Spring 框架提供的响应式编程模型&#xff0c;旨在支持非阻塞、异步和高并发的应用场景。其优缺点如下&#xff1a; 优点 高并发与低资源消耗 非阻塞 I/O&#xff1a;基于事件循环模型&#xff08;如 Netty&#xff09;&am…

DeepSeek 硅基流动

DeepSeek 硅基流动 &#x1f381; 四大神仙优势&#x1f31f; 三步拥有官网同款671B大模型1️⃣ 戳这里&#x1f449; 国内直连通道2️⃣ 复制API密钥3️⃣ 安装Chatbox贴进软件秒变AI大佬 &#x1f4c1; 网盘地址&#xff1a;&#xff08;所用到的软件可以直接下载&#xff09…

mysql 学习10 多表查询 -多表关系,多表查询

多表关系 一对多 多对多 创建学生表 #多对多表 学生选课系统create table student(id int primary key auto_increment comment 主键ID,name varchar(64) comment 姓名,studentnumber varchar(10) comment 学号 )comment 学生表;insert into student(id,name,studentnumber)va…

云端IDE如何重定义开发体验

豆包 MarsCode 是一个集成了AI功能的编程助手和云端IDE&#xff0c;旨在提高开发效率和质量。它支持多种编程语言和IDE&#xff0c;提供智能代码补全、代码解释、单元测试生成和问题修复等功能&#xff0c;同时具备AI对话视图和开发工具。 豆包 MarsCode 豆包 MarsCode 编程助…

redis之RDB持久化过程

redis的rdb持久化过程 流程图就想表达两点&#xff1a; 1.主进程会fork一个子进程&#xff0c;子进程共享主进程内存数据(fork其实是复制页表)&#xff0c;子进程读取数据并写到新的rdb文件&#xff0c;最后替换旧的rdb文件。 2.在持久化过程中主进程接收到用户写操作&#x…

15.PPT:文静-云计算行业发展【29】

目录 NO123​ NO345​ NO6​ NO78 NO9/10/11/12​ NO123 设计→幻灯片大小→自定义幻灯片大小→ 全屏显示&#xff08;16&#xff1a;9&#xff09;→最大化 NO345 SmartArt 主题颜色2/6/9&#xff1a;形状样式&#xff1a;样式 加大行距加宽间距 NO6 NO78 设计→设置背景…

deepseek本地部署,使用python交互运行

deepseek Github 地址&#xff1a;https://github.com/deepseek-ai/DeepSeek-R1 在Github中我们看到这样的图片&#xff0c;模型参数等都可以通过HuggingFace下载&#xff0c;DeepSeek-R1-Distill-Qwen-参数量&#xff0c;参数量越大&#xff0c;对显存的要求更高 我们以参数量…

SpringUI Web高端动态交互元件库

Axure Web高端动态交互元件库是一个专为Web设计与开发领域设计的高质量资源集合&#xff0c;旨在加速原型设计和开发流程。以下是关于这个元件库的详细介绍&#xff1a; 一、概述 Axure Web高端动态交互元件库是一个集成了多种预制、高质量交互组件的工具集合。这些组件经过精…

Spring Boot整合MQTT

MQTT是基于代理的轻量级的消息发布订阅传输协议。 1、下载安装代理 进入mosquitto下载地址&#xff1a;Download | Eclipse Mosquitto&#xff0c;进行下载&#xff0c;以win版本为例 下载完成后&#xff0c;在本地文件夹找到下载的代理安装文件 使用管理员身份打开安装 安装…

网络数据请求

1.GET和POST请求 1.1发送GET请求 1.2发送POST请求 1.3 在页面刚加载的时候请求数据 2.request请求的注意事项

【OpenCV实战】基于 OpenCV 的多尺度与模板匹配目标跟踪设计与实现

文章目录 基于 OpenCV 的模板匹配目标跟踪设计与实现1. 摘要2. 系统概述3. 系统原理3.1 模板匹配的基本原理3.2 多尺度匹配 4. 逻辑流程4.1 系统初始化4.2 主循环4.3 逻辑流程图 5. 关键代码解析5.1 鼠标回调函数5.2 多尺度模板匹配 6. 系统优势与不足6.1 优势6.2 不足 7. 总结…

数据结构与算法学习笔记----博弈论

# 数据结构与算法学习笔记----博弈论 author: 明月清了个风 first publish time: 2025.2.6 ps⭐️包含了博弈论中的两种问题Nim游戏和SG函数&#xff0c;一共四道例题&#xff0c;给出了具体公式的证明过程。 Acwing 891. Nim游戏 [原题链接](891. Nim游戏 - AcWing题库) 给…

deepseek本地部署

DeepSeek本地部署详细指南 DeepSeek作为一款开源且性能强大的大语言模型&#xff0c;提供了灵活的本地部署方案&#xff0c;让用户能够在本地环境中高效运行模型&#xff0c;同时保护数据隐私&#xff0c;这里记录自己DeepSeek本地部署流程。 主机环境 cpu:amd 7500Fgpu:406…

VUE 集成企微机器人通知

message-robot 便于线上异常问题及时发现处理&#xff0c;项目中集成企微机器人通知&#xff0c;及时接收问题并处理 企微机器人通知工具类 export class MessageRobotUtil {constructor() {}/*** 发送 markdown 消息* param robotKey 机器人 ID* param title 消息标题* param…

消防救援营区管理2024年度回顾与分析

2024年&#xff0c;消防救援营区管理领域在挑战与机遇并存的环境中取得了显著进展。站在产业和行业的角度&#xff0c;对这一年的回顾具有重要意义。 营区设施管理方面&#xff0c;基础设施建设与维护工作成效显著。 老旧营房的修缮确保了消防员居住环境的安全舒适&#xff0c;…

趣解单词,实现快速记忆

英文单词 love&#xff0c;是“爱”的意思&#xff1a; love v./n.爱&#xff1b;喜欢&#xff1b;热爱&#xff1b;爱情&#xff1b;心爱的人 那什么是爱呢&#xff1f;love&#xff0c;首字母为l&#xff0c;是一根绳子&#xff0c;ve-通f&#xff0c;love通life&#xff0…