【Linux】pthread多线程基础

article/2025/7/4 23:57:20

参考博客:https://blog.csdn.net/Alkaid2000/article/details/128121066

线程概述

  • 与进程类似,线程(thread)是允许应用程序并发执行多个任务的一种机制。一个进程可以包含多个线程。同一个程序中的所有线程均会独立执行相同程序,且共享同一份全局内存区域,其中包括初始化数据段、未初始化数据段、以及堆内存段。(传统意义上的Unix进程只是多线程程序的一个特例,该进程只会包含一个线程

  • 进程是CPU分配资源的最小单位,线程是操作系统调度执行的最小单位

  • 线程是轻量级的进程(LWP:Light Weight Process),在Linux环境下线程的本质仍是进程。

  • 查看指定的进程的LWP号ps -Lf pid

线程和进程的区别

  • 进程间的信息难以共享。由于除去只读代码段外,父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。

  • 调用fork()来创建进程的代价相对较高,即便存在这写时拷贝的这个技术,仍然需要复制诸如内存页表和文件描述符表之类的多种进程属性,这意味着fork()调用在时间上的开销依然不菲。

  • 线程之间能够方便、快速地共享信息。只需将数据复制到共享(全局或堆)变量中即可。创建进程通常要快十倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表

线程和进程的虚拟地址空间

  • 对于进程,由于写时拷贝的技术,当创建了一个子进程的时候,他会进行写时拷贝,把上面的东西再复制一份。

  • 但是对于线程,由于其共享内存,所以他不会复制这个表,它只会在栈空间以及代码段将其进行分区,把每个线程都在这个段中分配好自己的内存空间。

在这里插入图片描述

线程之间共享的非共享资源

共享资源有如下

进程ID和父进程ID
进程组ID和会话ID
用户ID和用户组ID
文件描述符表
信号处置
文件系统的相关信息:文件权限掩码(umask)、当前工作目录
虚拟地址空间(除栈、.text)
其实以上总的来说是内核的数据。

非共享资源

线程ID
信号掩码
线程特有的数据
error变量
实时调度策略和优先级
栈,本地变量和函数的调用链接信息

NPTL

  • 当Linux最初开发时,在内核中并不能真正使用线程。但是它的确可以通过clone()系统调用将进程作为可调度的实体。这个调用创建了调用进程的一个拷贝,这个拷贝与调度进程共享相同的地址空间。LinuxThreads项目使用这个调用来完全在用户空间模拟对线程的支持。不幸的是,这种方法有一些缺点,尤其是在信号处理、调度和进程间同步等方面都存在问题。另外,这个线程模型也不符合POSIX的要求。

  • 要改进的LinuxThreads,需要内核的支持,并且重写线程库。有两个相互竞争的项目开始来满足这些要求。一个包括IBM的开发人员的团队开展了NGPT(Next-Generation POSIX Threads)项目。同时,Red Hat的一些开发人员开展了NPTL项目。NGPT在2003年中期被放弃了,把这个领域完全留给了NPTL。

  • NPTL,或称为Native POSIX Thread Library,是Linux线程的一个新实现,它克服了LinuxThreads的缺点,同时也复合了POSIX的需求。与LinuxThreads相比,它的性能和稳定性方面都提供了重大的改进。

  • 以查看自己的pthread版本:getconf GNU_LIBPTHREAD_VERSION

在这里插入图片描述

创建线程

使用函数pthread_create(),一般情况下,main函数所在的线程我们称之为主线程(main线程),其余创建的线程称之为子线程。程序中默认只有一个进程,使用fork函数进行调用的话,会产生两个进程;程序中默认只有一个线程,pthread_create()函数调用,2个线程。

  • 在这个函数的说明文档中解释了,线程退出的四个前提:
    1. 当线程主动调用 pthread_exit(3) 时,需传入一个 “退出状态值”(如动态分配的结果、常量等)。其他线程可通过 pthread_join(3) 阻塞等待该线程结束,并获取这个退出状态。

    2. 线程从 start_routine(线程函数)返回时,等价于自动调用 pthread_exit(3),返回值会被当作退出状态传递。

    3. 其他线程调用 pthread_cancel(3) 向该线程发送 “取消请求”,若线程响应取消(默认允许取消且为延迟取消类型),则会终止。
      注意:
      取消并非 “立即执行”,线程会在 取消点(如 sleep、read 等系统调用,或手动 pthread_testcancel())响应。
      被取消的线程,pthread_join 获取的退出状态通常是 PTHREAD_CANCELED(即 (void*)-1 )。

    4. 若进程中任意线程调用 exit(3),或主线程从 main() 返回,整个进程终止,所有线程(无论是否执行完)都会被强制结束。
      区别线程终止与进程终止:
      线程终止(如 pthread_exit):仅当前线程结束,其他线程不受影响。
      进程终止(如 exit 或 main 返回):所有线程一起终止,进程资源被回收。

在这里插入图片描述

void *task(void *arg)
{std::cout << "thread starts ..." << std::endl;std::cout << "value is " << *(static_cast<int *>(arg)) << std::endl;std::cout << "thread ends" << std::endl;std::cout << "pid of task :" << pthread_self() << std::endl;std::cout << "task thread finished" << std::endl;return NULL;
}void test1()
{pthread_t thread_id;int value = 100;int ret = pthread_create(&thread_id, NULL, task, static_cast<void *>(&value));if (ret != 0){const char *error = strerror(ret);std::cout << "error: " << error << std::endl;return;}usleep(100000);std::cout << "thread_id = " << thread_id << std::endl;std::cout << "pid of test1:" << pthread_self() << std::endl;for (int i = 0; i < 5; ++i){std::cout << "i = " << i << std::endl;}std::cout << "main finished" << std::endl; // 这里不会打印
}
API 详解:pthread_create()
  • 函数原型
int pthread_create(pthread_t *thread,          // 输出参数:线程IDconst pthread_attr_t *attr, // 线程属性,NULL表示默认属性void *(*start_routine) (void *), // 线程函数指针void *arg                   // 传递给线程函数的参数
);
  • 返回值
    • 成功:返回 0
    • 失败:返回错误码(如 EAGAIN、EINVAL 等)
执行流程
  1. 主线程创建子线程,传递参数&value
  2. 子线程打印信息,包括自己的线程 ID(pthread_self()返回)
  3. 主线程休眠 100ms(usleep(100000))等待子线程执行
  4. 主线程继续执行后续代码
关键点
  • 线程函数签名:必须是void* (*)(void*)类型
  • 参数传递:通过void*指针传递,需在函数内部转换回原类型
  • 线程 IDpthread_t类型,用于标识线程,不同系统实现可能不同
  • 错误处理:始终检查pthread_create()的返回值
API 详解:pthread_self

用于获取当前线程的线程 ID,常用来标识、区分不同线程,或在日志打印等场景记录当前执行线程信息。

函数原型
pthread_t pthread_self(void);

参数说明:无输入参数,直接调用即可获取当前线程的线程 ID。

返回值

返回当前线程对应的 pthread_t 类型的线程标识符,不同系统对 pthread_t 具体实现(如是否为整数、结构体等)有差异,但可用于线程相关操作(如比较线程 ID 等 )。

运行结果
  • 注意需要引入pthread库:-lpthread
    在这里插入图片描述

终止线程

演示线程主动退出的方法,以及pthread_exit()return的区别

void test2()
{pthread_t thread_id;int value = 100;int ret = pthread_create(&thread_id, NULL, task, (void *)(&value));if (ret != 0){const char *error = strerror(ret);std::cout << "error: " << error << std::endl;return;}usleep(10000);std::cout << "thread_id = " << thread_id << std::endl;std::cout << "pid of test2 :" << pthread_self() << std::endl;ret = pthread_equal(thread_id, pthread_self());std::cout << "equal : thread_id,pthread_self() :" << ret << std::endl;ret = pthread_equal(thread_id, thread_id);std::cout << "equal : thread_id,thread_id:" << ret << std::endl;pthread_exit(NULL); // 当前线程退出std::cout << "main finished" << std::endl; // 这里不会打印
}
核心代码
pthread_exit(NULL); // 终止当前线程
  • 参数
    • retval:线程的返回值,可通过pthread_join()获取
  • 说明
    • 终止当前线程,并将retval传递给等待该线程的其他线程
    • 如果主线程调用pthread_exit(),其他线程仍会继续执行
执行流程
  1. 主线程创建子线程后休眠 10ms
  2. 主线程调用pthread_equal()比较线程 ID
  3. 主线程调用pthread_exit()退出,不影响子线程继续执行
关键点
  • pthread_exit() vs return
    • return:从线程函数返回,效果与pthread_exit()相同
    • pthread_exit():可在任何地方调用,终止当前线程
API 详解:pthread_equal

用于比较两个线程 ID 是否属于同一个线程,判断逻辑由系统实现,能帮我们明确不同线程标识是否指向同一实际执行线程。

函数原型
int pthread_equal(pthread_t t1, pthread_t t2);

参数说明

  • t1:第一个待比较的线程 ID,通过 pthread_create 输出参数或 pthread_self 获取。
  • t2:第二个待比较的线程 ID,来源同 t1
返回值
  • t1t2 表示的是同一个线程,返回非 0 值(不同系统实现可能返回具体非零整数,通常可直接用 != 0 判断相等 )。
  • t1t2 表示不同线程,返回 0 。
运行结果

在这里插入图片描述

连接已终止的线程

  • 当子进程结束需要回收资源时,需要父进程来进行回收,主动的方式是,尽管父进程结束,它也会调用初始化程序来回收这个子进程,被动的方式是,使用wait或者是waitpid函数来等待子进程结束

  • 但是对于线程来说,任意一个线程,都可以回收要结束的线程资源,不一定需要通过父线程。所以为什么需要连接,因为连接后才可以让其资源得到释放,否则会产生僵尸线程

  • 演示如何在线程结束时返回数据,并在主线程中获取该数据。

void *task3(void *arg)
{std::cout << "task3 thread_id:" << pthread_self() << std::endl;int *pValue = (int *)malloc(sizeof(int));*pValue = *static_cast<int *>(arg) + 100;pthread_exit(pValue); // 返回地址,实际作为task3的返回值return NULL;
}void test3()
{pthread_t thread_id;int value = 100;int ret = pthread_create(&thread_id, NULL, task3, (void *)(&value));if (ret != 0){const char *error = strerror(ret);std::cout << "error: " << error << std::endl;return;}void *retValue = NULL;ret = pthread_join(thread_id, &retValue); // 阻塞if (ret != 0 || retValue == NULL){if (retValue == NULL){std::cerr << "retValue is NULL" << std::endl;return;}const char *error = strerror(ret);std::cout << "error: " << error << std::endl;return;}std::cout << "retValue = " << *static_cast<int *>(retValue) << std::endl;std::cout << "test3 finished!" << std::endl;free(retValue);
}
核心代码
// 线程函数中
int *pValue = (int *)malloc(sizeof(int));
*pValue = *static_cast<int *>(arg) + 100;
pthread_exit(pValue); // 返回堆上分配的数据// 主线程中
void *retValue = NULL;
pthread_join(thread_id, &retValue); // 获取返回值
std::cout << "retValue = " << *static_cast<int *>(retValue) << std::endl;
free(retValue); // 释放内存
API 详解:pthread_join()

函数原型

int pthread_join(pthread_t thread,   // 要等待的线程IDvoid **retval       // 输出参数:存储线程的返回值
);
  • 返回值
    • 成功:返回 0
    • 失败:返回错误码(如 EDEADLK、ESRCH 等)
  • 说明
    • 阻塞当前线程,直到指定线程结束
    • 获取线程的返回值(通过pthread_exit()return设置)
关键点
  • 内存管理
    • 线程返回值必须是堆上分配的内存(如malloc/new
    • 栈上分配的内存(如局部变量)在线程结束后会失效
  • 类型转换
    • 返回值类型为void*,需转换回实际类型
  • 资源释放
    • 必须手动释放返回的内存,避免内存泄漏
运行结果

在这里插入图片描述

线程的分离
  • 演示如何将线程设置为分离状态,使其结束后自动释放资源。
void* task4(void *arg)
{std::cout << "task4 started!" << std::endl;sleep(5);std::cout << "task4 finished!" << std::endl;return NULL;
}void test4()
{pthread_t thread_id;int value = 100;int ret = pthread_create(&thread_id, NULL, task4, (void *)(&value));if (ret != 0){const char *error = strerror(ret);std::cout << "error: " << error << std::endl;return;}ret = pthread_detach(thread_id); // 非阻塞std::cout << "pthread_detach!" << std::endl;if (ret != 0){const char *error = strerror(ret);std::cout << "error: " << error << std::endl;return;}ret = pthread_join(thread_id, NULL); // 这里会报错if (ret != 0){const char *error = strerror(ret);std::cout << "error: " << error << std::endl;return;}pthread_exit(NULL);
}
核心代码
pthread_detach(thread_id); // 设置线程为分离状态
API 详解:pthread_detach()

函数原型

int pthread_detach(pthread_t thread); // 线程ID
  • 返回值

    • 成功:返回 0
    • 失败:返回错误码(如 EINVAL、ESRCH 等)
  • 说明

    • 将指定线程设置为分离状态
    • 分离状态的线程结束后,系统自动回收其资源,无需pthread_join()
执行流程
  1. 主线程创建子线程(执行task4,休眠 5 秒)
  2. 主线程调用pthread_detach()将子线程设置为分离状态
  3. 主线程尝试调用pthread_join()等待子线程,失败并输出错误
关键点
  • 分离状态特点

    • 不能对分离状态的线程调用pthread_join()
    • 适合后台运行且无需返回值的线程(如守护线程)
  • 错误处理

    • 分离已分离的线程会失败(返回 EINVAL)
    • 对分离状态的线程调用pthread_join()会失败(返回 EINVAL)
运行结果

在这里插入图片描述

线程取消
  • 线程检查是否被取消并按照请求进行动作的一个位置。取消点也是线程检测是否被取消的一个位置。
  • pthreads标准制定了几个取消点,其中包括:
    1. 通过pthread_testcancel调用以编程方式建立线程取消点。
    2. 线程等待pthread_cond_wait或pthread_cond_timewait()中的特定条件。 (错误的程序设计可能会在取消时导致死锁)
    3. 被sigwait()阻塞的函数。
    4. 一些标准的库调用。通常,这些调用包括线程可基于阻塞的函数。

那么这个函数pthread_cancel到底做了什么:

  • 线程默认是可以被取消的。
  • pthread_cancel函数只是给线程发送一个取消请希望可以将线程终止。
  • 对于接收请求的线程来说,只是一种建议。
  • 接收到的取消请求线程可能会马上停止,也可能不会直到遇到一个取消点之后

下面演示如何请求取消一个线程,并处理取消后的状态

void* task4(void *arg)
{std::cout << "task4 started!" << std::endl;sleep(5);std::cout << "task4 finished!" << std::endl;return NULL;
}void test5()
{pthread_t thread_id;int value = 100;int ret = pthread_create(&thread_id, NULL, task4, (void *)(&value));if (ret != 0){const char *error = strerror(ret);std::cout << "error: " << error << std::endl;return;}sleep(1);std::cout << "thread id before :" << thread_id << std::endl;pthread_cancel(thread_id);std::cout << "thread has benn canceled!" << std::endl;std::cout << "thread id after:" << thread_id << std::endl;void *pValue = NULL;ret = pthread_join(thread_id, &pValue);if (ret != 0){const char *error = strerror(ret);std::cout << "error: " << error << std::endl;return;}if (pValue == NULL){std::cout << "pValue is NULL" << std::endl;}else{std::cout << "pValue address = " << pValue << std::endl;}pthread_exit(NULL);
}
核心代码
pthread_cancel(thread_id); // 请求取消线程
API 详解:pthread_cancel()

函数原型

int pthread_cancel(pthread_t thread); // 线程ID
  • 返回值
    • 成功:返回 0
    • 失败:返回错误码(如 ESRCH)
  • 说明
    • 发送取消请求给指定线程
    • 线程是否响应取消请求取决于其取消状态和类型
执行流程
  1. 主线程创建子线程(执行task4,休眠 5 秒)
  2. 主线程休眠 1 秒确保子线程已启动
  3. 主线程调用pthread_cancel()请求取消子线程
  4. 主线程调用pthread_join()等待子线程结束并获取返回值
关键点
  • 取消状态与类型
    • 默认情况下,线程可以响应取消请求(CANCEL_ENABLE)
    • 取消类型分为延迟取消(CANCEL_DEFERRED,默认)和立即取消(CANCEL_ASYNCHRONOUS)
  • 取消点
    • 线程在某些系统调用(如sleep()read())处会检查取消请求
    • 可通过pthread_testcancel()手动设置取消点
  • 返回值
    • 成功取消的线程通常返回PTHREAD_CANCELED((void*)-1)
运行结果

在这里插入图片描述

线程属性

对于结构体pthread_attr_t,这个结构体包含了所有的线程属性的信息。所以我们需要对这个结构体进行操作,才可以更改其进程属性,其实无非就是开辟一块内存,然后存放其相应的信息,主要涉及的函数有如下:

  • 通过一下命令可以查看对应的设置属性函数
man -k pthread_attr_

在这里插入图片描述

  • 演示如何使用线程属性对象(pthread_attr_t)设置线程的分离状态和栈大小。
void* task4(void *arg)
{std::cout << "task4 started!" << std::endl;sleep(5);std::cout << "task4 finished!" << std::endl;return NULL;
}void test6(){pthread_t thread_id;pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); //设置线程启动属性:detachint value = 100;int ret = pthread_create(&thread_id, &attr, task4, (void *)(&value));std::cout << "detach not blocking !" << std::endl;if (ret != 0){const char *error = strerror(ret);std::cout << "error: " << error << std::endl;return;}size_t size = 0;ret = pthread_attr_getstacksize(&attr,&size);std::cout << "pthread get statck size: " << size << std::endl;int state = 0;ret = pthread_attr_getdetachstate(&attr,&state);std::cout << "pthread detach state :" << state << std::endl;sleep(10);ret = pthread_join(thread_id, NULL); //无法join了if (ret != 0){const char *error = strerror(ret);std::cout << "error: " << error << std::endl;return;}pthread_attr_destroy(&attr);}
核心代码
pthread_attr_t attr;
pthread_attr_init(&attr); // 初始化属性对象
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 设置为分离状态// 创建线程时使用属性对象
pthread_create(&thread_id, &attr, task4, (void *)(&value));// 获取属性信息
size_t size = 0;
pthread_attr_getstacksize(&attr, &size); // 获取栈大小int state = 0;
pthread_attr_getdetachstate(&attr, &state); // 获取分离状态
API 详解:线程属性函数

初始化与销毁

int pthread_attr_init(pthread_attr_t *attr); // 初始化属性对象
int pthread_attr_destroy(pthread_attr_t *attr); // 销毁属性对象

设置分离状态

int pthread_attr_setdetachstate(pthread_attr_t *attr,       // 属性对象int detachstate             // 分离状态:PTHREAD_CREATE_DETACHED 或 PTHREAD_CREATE_JOINABLE
);
int pthread_attr_getdetachstate(pthread_attr_t *attr,       // 属性对象int *detachstate            // 输出参数:当前分离状态
);

设置栈大小

int pthread_attr_setstacksize(pthread_attr_t *attr,       // 属性对象size_t stacksize            // 栈大小(字节)
);
int pthread_attr_getstacksize(pthread_attr_t *attr,       // 属性对象size_t *stacksize           // 输出参数:当前栈大小
);
执行流程
  1. 初始化线程属性对象并设置为分离状态
  2. 使用该属性创建线程
  3. 获取并打印线程的栈大小和分离状态
  4. 主线程休眠 10 秒,等待子线程执行(虽然子线程是分离的)
  5. 尝试pthread_join()已分离的线程,失败并输出错误
关键点
  • 属性对象生命周期
    • 使用前必须初始化(pthread_attr_init()
    • 使用后必须销毁(pthread_attr_destroy()
  • 分离状态的两种设置方式
    1. 创建线程后调用pthread_detach()
    2. 创建线程前通过属性对象设置(PTHREAD_CREATE_DETACHED
  • 栈大小
    • 默认栈大小由系统决定(通常为几 MB)
    • 可通过pthread_attr_setstacksize()调整,需注意栈溢出风险
运行结果

在这里插入图片描述

更多资料:https://github.com/0voice


http://www.hkcw.cn/article/OMCsxnInDG.shtml

相关文章

瑞萨CS+ for CC V8.13.00环境安装教程

前言&#xff1a;最近接触到瑞萨的芯片&#xff0c;需要安装对应的集成开发环境&#xff0c;发现这与ARM内核的单片机存在很大的不同&#xff0c;这里先简单介绍一下其IDE的安装配置方式。 1&#xff0c;官网下载 瑞萨半导体开发环境安装网址 CS | Renesas 当然在下载安装包之…

【知识点】第3章:基本数据类型

文章目录 知识点整理数字类型字符类型 练习题判断题程序题 知识点整理 数字类型 Python语言提供整数、浮点数、复数3种数字类型。 不同进制的引导符号&#xff1a; 不考查进制间的转换。 浮点数类型与数学中实数的概念一致&#xff0c;表示带有小数的数值。Python语言要求所…

【算法】回溯法

一、回溯法的基本思想 回溯法有“通用解题方法”的美称&#xff0c;解题过程是一个搜索过程。在搜索尝试过程中寻找问题的解&#xff0c;当发现已不满足求解条件时&#xff0c;就“回溯”返回&#xff08;也就是递归返回&#xff09;&#xff0c;尝试别的路径。因此&#xff0…

AIGC 基础篇 高等数学篇 01函数与极限

声明&#xff1a;本文章仅用于博主本人复习&#xff0c;请不要将本文章当成预习篇或者讲解篇 此外&#xff0c;此文章不会包含全部的高等数学知识&#xff0c;仅仅是为了学习AI而进行的前期学习&#xff0c;因此知识含量不会很多&#xff0c;由于博主是第一次尝试做&#xff0…

如何在 Windows 11 Home 版上下载和安装 Hyper-V

Windows 11 Home 版与之前的微软操作系统版本一样,没有自带 Hyper-V 管理器。因此,如果您想在 Windows 11 Home 上下载和安装 Hyper-V,以下是详细的步骤教程。 Hyper-V 是微软提供的一种虚拟化解决方案,允许用户为各种操作系统创建虚拟机。与 VMware 或 VirtualBox 不同,…

C++ --- string类的简单实现

string类的简单实现 前言1、基本成员2、构造方法和析构方法2.1无参构造2.2有参构造2.3析构函数2.4拷贝构造函数 3、遍历方式3.1operator [ ]3.2iterator3.2.1正向迭代器3.2.2const正向迭代器 3.3范围for 4、常用方法&#xff0c;运算符重载c_str()size()reverse()push_back()po…

ESP32之Linux编译环境搭建流程

背景&#xff1a;为了解决 “windows环境中编译ESP32代码速度慢” 的问题&#xff0c;现搭建一个Linux环境&#xff0c;让windows下的VScode连接到Linux环境&#xff0c;VSCode负责编辑代码&#xff0c;虚拟机用于编译代码。 目录 一、安装VMware 1.1 获取VMware安装包 1.2…

Python-matplotlib中的Pyplot API和面向对象 API

matplotlib中的Pyplot API和面向对象 API Pyplot API&#xff08;状态机模式&#xff09;面向对象 API 详解二者差别核心区别方法命名差异注意事项差别举例 &#x1f345; Pyplot API&#xff08;状态机模式&#xff09;和面向对象 API 是两种不同的编程接口.&#x1f345; 它们…

BUUCTF之[ACTF2020 新生赛]BackupFile

打开环境就一句话 找出源文件! 结合题目名字&#xff1a;BackupFile 先用dirsearct扫描网站文件 发现一个index.php.bak ,拼接url下载 打开发现php代码 <?php include_once "flag.php";if(isset($_GET[key])) {$key $_GET[key];if(!is_numeric($key)) {exit…

Spring Boot 3.X 下Redis缓存的尝试(一):初步尝试

背景 想像一下有这么一个场景&#xff0c;一个系统有超多角色、角色下有多个菜单、菜单下有多个按钮权限&#xff0c;这种子父级关系查询每次向数据库查询相当耗时&#xff0c;那么我们是否可以将这种更新频次不高&#xff0c;而查询耗时的数据且不直接影响业务的数据放进缓存中…

基于springboot的民间文化艺术品销售系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业多年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

9 动态规划

9.3 爬楼梯 从1开始举例子发现规律 dp[i]dp[i-1]dp[i-2]; class Solution { public:int climbStairs(int n) {if(n<1){return 1;}vector<int>dp(n1);dp[2]2;dp[1]1;for(int i3;i<n;i){dp[i]dp[i-1]dp[i-2];}return dp[n];} }; 9.29 打家劫舍 1 确定dp数组下标与…

Playwright 测试框架 - Node.js

🚀超全实战:基于 Playwright + Node.js 的自动化测试项目教程【附源码】 📌 本文适合自动化测试入门者 & 前端测试实战者。从零开始手把手教你搭建一个 Playwright + Node.js 项目,涵盖配置、测试用例编写、运行与调试、报告生成以及实用进阶技巧。建议收藏!👍 �…

4.RV1126-OPENCV 图像轮廓识别

一.图像识别API 1.图像识别作用 它常用于视觉任务、目标检测、图像分割等等。在 OPENCV 中通常使用 Canny 函数、findContours 函数、drawContours 函数结合在一起去做轮廓的形检测。 2.常用的API findContours 函数&#xff1a;用于寻找图片的轮廓&#xff0c;并把所有的数…

Cursor从入门到精通实战指南(五):一键生成流程图/架构图,开发者必备收藏!

解锁Cursor&#xff1a;开启高效开发新境界 结合了GPT-4、Claude 3.5等强大的大语言模型&#xff0c;能够通过自然语言交互实现代码生成、原型设计、流程优化等功能。无论是编程新手还是经验丰富的开发者&#xff0c;都能借助Cursor的智能特性&#xff0c;快速完成复杂的编码任…

postman工具使用

基本功能操作 常用断言 定义&#xff1a;postman 断言借助 JavaScript - js 语言编写代码&#xff0c;自动判断预期结果与实际结果是否一致。&#xff08; 注意断言 代码写在 Tests 的标签中&#xff09; 断言响应状态码 断言响应体是否包含某个字符串&#xff08;Response bo…

【Elasticsearch】Elasticsearch 核心技术(一):索引

Elasticsearch 核心技术&#xff08;一&#xff09;&#xff1a;索引 1.索引的定义2.索引的命名规范3.索引的增、删、改、查3.1 创建索引3.1.1 创建空索引 3.2 删除索引3.3 文档操作3.3.1 添加/更新文档&#xff08;指定ID&#xff09;3.3.2 添加文档&#xff08;自动生成ID&am…

玩客云 OEC/OECT 笔记(2) 运行RKNN程序

目录 玩客云 OEC/OECT 笔记(1) 拆机刷入Armbian固件玩客云 OEC/OECT 笔记(2) 运行RKNN程序 RKNN OEC/OEC-Turbo 使用的芯片是 RK3566/RK3568, 这个系列是内建神经网络处理器 NPU 的, 利用 RKNN 可以部署运行 AI 模型利用 NPU 硬件加速模型推理. 要使用 NPU, 首先需要在电脑使…

【音视频】FFmpeg 硬件(NVDIA)编码H264

FFmpeg 与x264的关系 ffmpeg软编码是使⽤x264开源项⽬&#xff0c;也就是说ffmpeg软编码H264最终是调⽤了x264开源项⽬&#xff0c;所以我们要先理解ffmpeg和x264的调⽤关系&#xff0c;这⾥我们主要关注x264_init。对于x264的参数都在 ffmpeg\libavcodec \libx264.c x264\co…

深度学习和神经网络 卷积神经网络CNN

1.什么是卷积神经网络 一种前馈神经网络&#xff1b;受生物学感受野的机制提出专门处理网格结构数据的深度学习模型 核心特点&#xff1a;通过卷积操作自动提取空间局部特征&#xff08;如纹理、边缘&#xff09;&#xff0c;显著降低参数量 2.CNN的三个结构特征 局部连接&a…