linux:线程的控制

在这里插入图片描述

个人主页 : 个人主页
个人专栏 : 《数据结构》 《C语言》《C++》《Linux》

文章目录

  • 前言
  • 一、线程的总结
    • 1. 线程的优点
    • 2. 线程的缺点
    • 3. 线程异常
    • 4.线程和进程
  • 二、线程的控制
    • 创建线程
    • 线程终止
    • 线程等待
      • 获取返回值
    • 线程分离
  • 总结


前言

本文作为我对于线程的简单总结,线程控制的知识总结


一、线程的总结

1. 线程的优点

  • 创建一个新线程的代价比创建一个新进程小的多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要小
  • 线程占有的资源要比进程少很多
  • 能充分利用多处理器的可并行数量(并行,多个执行流在同一时刻拿着不同的CPU继续运算,执行代码)
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • I/O密集型应用(如下载,上传),为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作

创建线程并不是越多越好,进程的执行效率,一定随着线程的数量增多,效率为正态分布的(线程的切换)。线程的个数最好 = CPU的个数 * CPU的核数


2. 线程的缺点

  • 性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与其他线程共享同一个处理器,如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,如增加了额外的同步和调度开销,而可用的资源不变
  • 健壮性(鲁棒性)降低:编写多线程需要全面更深入的考虑,在一个多线程程序里,因时间分配的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,也就是说线程之间是缺乏保护的
  • 缺乏访问控制:因为线程共享地址空间,那一个线程定义的局部变量 or new出的空间,其它线程也能使用,这就导致一个线程可能会不小心修改另一个线程正在使用的数据,导致数据不一致、逻辑错误甚至程序崩溃
  • 编程难度提高:编写与调试一个多线程程序比单线程程序困难

3. 线程异常

  • 单个线程如果出现除0,野指针问题导致线程崩溃,进程也会崩溃
  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也全部终止

创建5个线程,其中线程thread-4触发除0异常。

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <functional>
#include <time.h>
#include <vector>using func_t = std::function<void()>;
const int threadnum = 5;class ThreadDate
{
public:ThreadDate(const std::string &name, const uint64_t &time, func_t f): threadname(name), createtime(time), func(f){}public:std::string threadname;uint64_t createtime;func_t func;
};void Print()
{std::cout << "执行的任务..." << std::endl;
}void *threadRoutine(void *args)
{ThreadDate *td = static_cast<ThreadDate *>(args);while (true){std::cout << "new thread "<< " name: " << td->threadname << " create time: " << td->createtime << std::endl;td->func();if(td->threadname == "thread-4"){std::cout << td->threadname << "触发异常" << std::endl;int a = 10;a /= 0;}sleep(1);}
}int main()
{std::vector<pthread_t> pthreads;for (int i = 0; i < threadnum; ++i){char threadname[64];snprintf(threadname, sizeof(threadname), "%s-%d", "thread", i);pthread_t tid;ThreadDate *td = new ThreadDate(threadname, (uint64_t)time(nullptr), Print);pthread_create(&tid, nullptr, threadRoutine, (void *)td);pthreads.push_back(tid);sleep(1);}while (true){std::cout << "main thread" << std::endl;sleep(1);}return 0;
}

在这里插入图片描述
其中thread-4线程触发异常,进程直接终止。


4.线程和进程

  • 进程是资源分配的基本单位
  • 线程是调度的基本单位
  • 线程共享进程数据,但也有自己的一部分独立的数据(线程ID,线程的上下文数据独立的栈结构(pthread库维护),errno,信号屏蔽字,调度优先级)
  • 进程的多个线程共享同一个地址空间,因此地址空间的代码段,数据段都是共享的,线程还共享进程的文件描述符表,每种信号的处理方式(SIG_IGN,SIG_DFL,自定义的信号处理函数),当前工作目录(cwd),用户id和组id

进程和线程的关系如下:
在这里插入图片描述

二、线程的控制

创建线程

在这里插入图片描述

函数原型: int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
功能:创建一个新的线程
参数:thread:返回线程ID
attr:设置线程属性,attr为nullptr表示使用默认属性
start_routine:是函数指针,为线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0,失败返回错误码
错误检查:传统的一些函数,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。pthreads函数出错时不会设置全局变量errno,而是将错误码通过返回值返回。pthreads也同样提供线程内errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值的判定,因为读取返回值要比读取线程内的errno变量更方便。

下面我们要创建一个新线程循环打印"new thread’,主线程循环打印"main thread"

#include <iostream>
#include <pthread.h>
#include <unistd.h>void *threadRoutine(void *args)
{while(true){std::cout << "new thread" << std::endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, nullptr);while(true){std::cout << "main thread" << std::endl;sleep(1);}return 0;
}

在这里插入图片描述
编译时会有报错,编译器不认识pthread_create这个函数,但我们不是加了头文件吗?
我们知道linux没有真正的线程,只有轻量级进程的概念,所以操作系统只会提供轻量级进程创建的系统调用,不会直接提供线程创建的接口。linux为了解决这一问题,就在软件层创建了pthread原生线程库对用户提供线程控制接口,在内部将线程与轻量级进程一一对应。

在这里插入图片描述

这样有什么好处?标准化和兼容性(pthread编写的多线程应用程序在遵循POSIX标准的各种Unix-like系统上(包括Linux)都具有很高的可移植性),灵活性:pthread库提供了一套丰富的API,用于线程的创建、管理、同步和通信。这使得开发者可以根据应用程序的需求灵活地创建和管理线程,实现复杂的并发和并行计算任务。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

结果如上。


如何给创建的线程传参?

在这里插入图片描述
如上,arg的类型是void*类型,表示任意类型的指针。也就是说,我们可以穿一个整形,字符串,结构体对象。如下传递结构体。

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <functional>
#include <time.h>using func_t = std::function<void()>;class ThreadDate
{
public:ThreadDate(const std::string &name, const uint64_t &time, func_t f):threadname(name), createtime(time), func(f){}
public:std::string threadname;uint64_t createtime;func_t func;
};void Print()
{std::cout << "执行的任务..." << std::endl;
}void *threadRoutine(void *args)
{ThreadDate * td = static_cast<ThreadDate*>(args);while(true){std::cout << "new thread "<< " name: " << td->threadname << " create time: " << td->createtime<< std::endl;td->func();sleep(1);}
}int main()
{pthread_t tid;ThreadDate * td = new ThreadDate("thread-1", (uint64_t)time(nullptr), Print);pthread_create(&tid, nullptr, threadRoutine, (void *)td);while(true){std::cout << "main thread" << std::endl;sleep(1);}return 0;
}

在这里插入图片描述


那如何一次创建多个线程?与创建多个子进程类似。

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <functional>
#include <time.h>
#include <vector>using func_t = std::function<void()>;
const int threadnum = 5;class ThreadDate
{
public:ThreadDate(const std::string &name, const uint64_t &time, func_t f): threadname(name), createtime(time), func(f){}public:std::string threadname;uint64_t createtime;func_t func;
};void Print()
{std::cout << "执行的任务..." << std::endl;
}void *threadRoutine(void *args)
{ThreadDate *td = static_cast<ThreadDate *>(args);while (true){std::cout << "new thread "<< " name: " << td->threadname << " create time: " << td->createtime << std::endl;td->func();sleep(1);}
}int main()
{std::vector<pthread_t> pthreads;for (int i = 0; i < threadnum; ++i){char threadname[64];snprintf(threadname, sizeof(threadname), "%s-%d", "thread", i);pthread_t tid;ThreadDate *td = new ThreadDate(threadname, (uint64_t)time(nullptr), Print);pthread_create(&tid, nullptr, threadRoutine, (void *)td);pthreads.push_back(tid);sleep(1);}while (true){std::cout << "main thread" << std::endl;sleep(1);}return 0;
}

在这里插入图片描述

在这里插入图片描述


下面代码,创建线程并通过返回值打印其线程id。

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>void *threadRoutine(void *args)
{std::string name = static_cast<const char*>(args);while(true){std::cout << "new thread, name: " << name << std::endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread-1");while(true){std::cout << "main thread, sub thread: " << tid << std::endl;sleep(1);}return 0;
}

在这里插入图片描述
在这里插入图片描述

很明显,创建出来的线程id与LWP并不同,那主线程的id值是否也与LWP不同?这我们就要了解一个接口pthread_self(线程获取自己的id)
在这里插入图片描述

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>void *threadRoutine(void *args)
{std::string name = static_cast<const char*>(args);while(true){std::cout << "new thread, name: " << name << "thread id is " << pthread_self() << std::endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread-1");while(true){std::cout << "main thread id " << pthread_self() << "sub thread id " << tid << std::endl;sleep(1);}return 0;
}

在这里插入图片描述
在这里插入图片描述
主线程的id与新线程的id明显不是LWP,那是什么呢?我们以十六进制打印看看。

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>// 转换为十六进制
std::string ToHex(pthread_t tid)
{char id[64];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}void *threadRoutine(void *args)
{std::string name = static_cast<const char*>(args);while(true){std::cout << "new thread, name: " << name << "thread id is " << ToHex(pthread_self()) << std::endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread-1");while(true){std::cout << "main thread id " << ToHex(pthread_self()) << std::endl;sleep(1);}return 0;
}

在这里插入图片描述
现在,这个线程id是不是很像地址? 没错,线程id就是地址。


线程终止

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

  • 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit函数
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>// 转换为十六进制
std::string ToHex(pthread_t tid)
{char id[64];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}void *threadRoutine(void *args)
{std::string name = static_cast<const char*>(args);int cnt = 5;while(cnt--){std::cout << "new thread, name: " << name << "thread id is " << ToHex(pthread_self()) << std::endl;sleep(1);}return nullptr; // 线程终止
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread-1");while(true){std::cout << "main thread id " << ToHex(pthread_self()) << std::endl;sleep(1);}return 0;
}

在这里插入图片描述
在这里插入图片描述

  • 线程可以调用pthread_exit终止自己
    在这里插入图片描述
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>// 转换为十六进制
std::string ToHex(pthread_t tid)
{char id[64];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}void *threadRoutine(void *args)
{std::string name = static_cast<const char*>(args);int cnt = 5;while(cnt--){std::cout << "new thread, name: " << name << "thread id is " << ToHex(pthread_self()) << std::endl;sleep(1);}//return nullptr; // 线程终止pthread_exit(nullptr);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread-1");while(true){std::cout << "main thread id " << ToHex(pthread_self()) << std::endl;sleep(1);}return 0;
}

在这里插入图片描述
在这里插入图片描述

  • 一个线程可以调用pthread_cancel终止同一进程中的另一个线程

在这里插入图片描述

  • 功能:取消一个执行中的线程
  • 参数 thread:线程id
  • 返回值:成功返回0,失败返回错误码

不能用exit函数来终止线程,这会导致这个进程被终止。

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>// 转换为十六进制
std::string ToHex(pthread_t tid)
{char id[64];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}void *threadRoutine(void *args)
{std::string name = static_cast<const char*>(args);int cnt = 5;while(cnt--){std::cout << "new thread, name: " << name << "thread id is " << ToHex(pthread_self()) << std::endl;sleep(1);}//return nullptr; // 线程终止exit(13);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread-1");while(true){std::cout << "main thread id " << ToHex(pthread_self()) << std::endl;sleep(1);}return 0;
}

在这里插入图片描述
在这里插入图片描述


线程等待

获取返回值

我们知道子进程退出时会有僵尸状态,需要父进程来等待子进程。那线程也要被等待吗?是的,线程也要被等待。线程退出没有等待,也会导致类似进程的僵尸问题。线程通过等待获取新线程的返回值。
在这里插入图片描述

  • thread:线程id
  • retval:指向一个指针,后者指向返回值
  • 返回值:等待成功返回0,失败返回错误码
    调用该函数的线程将以阻塞的方式等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join的终止状态是不同的:
  • 如果thread线程通过return返回,retval所指向的单元存放的是thread线程函数的返回值
  • 如果thread线程被别的线程调用pthread_cancel异常终止,retval所指向的单元存放的是常数PTHREAD_CANCELED
  • 如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数
  • 如果不想获取thread的返回值,可以将nullptr传给retval

以从线程return为例。

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>// 转换为十六进制
std::string ToHex(pthread_t tid)
{char id[64];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}void *threadRoutine(void *args)
{std::string name = static_cast<const char *>(args);int cnt = 5;while (cnt--){std::cout << "new thread, name: " << name << "thread id is " << ToHex(pthread_self()) << std::endl;sleep(1);}return nullptr; // 线程终止
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)"thread-1");std::cout << "main thread id " << ToHex(pthread_self()) << std::endl;int n = pthread_join(tid, nullptr);std::cout << "main thread done , n : " << n << std::endl;return 0;
}

在这里插入图片描述
在这里插入图片描述


线程分离

对于一个线程,如果主线程要等待它,则会阻塞等待导致主线程自己的任务效率低。如果主线程不等待它,则可能导致类似僵尸进程的状态,使资源泄漏。这时我们就可以将线程分离。
线程可以设置为分离属性,一个线程如果被设置为分离属性,则该线程在退出之后,不需要其它执行流回收该线程资源,而是由操作系统进行回收。

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成资源泄漏
  • 如果不关心线程的返回值,join是一种负担,我们可以告诉系统,当线程退出时,自动释放线程资源。
  • joinable和分离是冲突的,一个线程不能既是joinable又是分离的。如果等待一个分离的线程,函数会立即返回错误,错误码通常为EINVAL

在这里插入图片描述

  • thread:线程id
  • 返回值:成功返回0,失败返回错误码

分离的线程在同一个进程地址空间,相互的线程不会相互干扰,但是如果分离的线程崩溃,进程也会崩溃。

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>// 转换为十六进制
std::string ToHex(pthread_t tid)
{char id[64];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}void *threadRoutine(void *args)
{std::string name = static_cast<const char *>(args);int cnt = 5;while (cnt--){std::cout << "new thread, name: " << name << "thread id is " << ToHex(pthread_self()) << std::endl;sleep(1);}int a = 10;a /= 0; // 除0异常return nullptr; // 线程终止
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)"thread-1");std::cout << "main thread id " << ToHex(pthread_self()) << std::endl;sleep(10);pthread_cancel(tid);// int n = pthread_join(tid, nullptr);// std::cout << "main thread done , n : " << n << std::endl;return 0;
}

在这里插入图片描述
在这里插入图片描述


总结

以上就是我对于线程控制的总结。

在这里插入图片描述

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

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

相关文章

[技术杂谈]解决右键没有vscode打开选项的问题

问题&#xff1a; 点击鼠标右键没有‘使用vscode打开’的选项。 原因&#xff1a; 在安装时没有勾选相关选项 解决办法&#xff1a; 新建一个reg文件写入下面文件&#xff0c;注意替换自己真实Code.exe路径 Windows Registry Editor Version 5.00[HKEY_CLASSES_ROOT\*\she…

深入理解Java多线程与线程池:提升程序性能的利器

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; 目录 引言 一、实现多线程 1.1. 继承Thread类 1.2. 实现Runnab…

深入浅出计算机网络 day.1 概论③ 电路交换、分组交换和报文交换

人无法同时拥有青春和对青春的感受 —— 04.3.9 内容概述 01.电路交换、分组交换和报文交换 02.三种交换方式的对比 一、电路交换、分组交换和报文交换 1.电路交换 计算机之间的数据传送是突发式的&#xff0c;当使用电路交换来传送计算机数据时&#xff0c;其线路的传输效率一…

构建可靠的数据基础:HDFS的架构优势与基本操作

目录 写在前面一、 HDFS概述1.1 HDFS简介1.2 HDFS优缺点1.2.1 优点1.2.2 缺点 1.3 HDFS组成架构1.4 HDFS文件块大小 二、HDFS的Shell操作&#xff08;开发重点&#xff09;2.1 基本语法2.2 命令大全2.3 常用命令实操2.3.1 上传2.3.2 下载2.3.3 HDFS直接操作 三、HDFS的API操作3…

代码随想录算法训练营第四十六天| 139.单词拆分、背包总结

文章目录 1.单词拆分[2.背包总结] 1.单词拆分 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。 注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典中的单词可以重复使用。 示例 1…

使用VBA快速梳理多层级族谱(组织架构)

实例需求&#xff1a;族谱&#xff08;或者公司组织架构等&#xff09;都是典型的带有层级关系数据&#xff0c;例如下图中左侧表格所示。 A列为层级&#xff08;准确的讲是B列成员的层级&#xff09;&#xff0c;从一开始递增B列和C列为成员直接的父&#xff08;/母&#xff…

项目解决方案:视频监控接入和录像系统设计方案(下)

目 录 1.概述 2. 建设目标及需求 2.1建设总目标 2.2 需求描述 ​2.3 需求分析 3.设计依据与设计原则 3.1设计依据 3.2 设计原则 4.建设方案设计 4.1系统方案设计 4.2组网说明 5.产品介绍 5.1视频监控综合资源管理平台介绍 5.2视频录像服务器和存储 5.2.…

PostgreSQL中In, Exists在SQL查询中到底有无区别

前言 SQL查询当中&#xff0c;In和Exists子查询到底有无区别&#xff1f;记得很多年以前&#xff0c;确实是有相关的使用戒条的&#xff0c;或者说存在一些使用的惯用法。试图完全抹开两者的区别&#xff0c;就有点过了。 两者的主要区别&#xff1a; 从目的上讲&#xff0c…

数据库create详细用法

数据库版本&#xff1a;KingbaseES V008R006C008B0014 简介 本篇文章主要以kingbase为例介绍创建表的基本语法、使用案例和添加描述等方法&#xff0c;在目录2、目录3再详细介绍数据类型和列级约束。 文章目录如下 1. 基本用法 1.1. 基础语法 1.2. 基础案例 1.3. 添加描述 …

防御保护作业六

实验拓扑图&#xff1a; 配置过程&#xff1a; FW1 自定义服务ike 创建nat策略&#xff0c;让10.0.2.0/24访问192.168.1.0/24的流量不进行nat转换,并将这条策略置于nat策略最上面&#xff0c;优先匹配 FW3 测试

vmware添加新磁盘

文章目录 前言一、新增磁盘二、初始化磁盘1.查看2.初始化3.挂载 总结 前言 虚拟机磁盘空间很散乱&#xff0c;大部分都在/root和/home下不好操作&#xff0c;故考虑新增磁盘、增加挂载点。 一、新增磁盘 右键打开虚拟机设置 二、初始化磁盘 1.查看 fdisk -l2.初始化 …

基于SpringBoot在线考试系统

基于SpringBootHtmlJavascript css 的在线考试系统 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBootMyBatis 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 一、项目背景 现在大家正处于互联网加的时代&#xff0c;这个时代它就是一个信息内…

Java 学习和实践笔记(33):多态详解

多态&#xff08;polymorphism): 不同的对象&#xff0c;调用同一个方法&#xff0c;对象的行为状态可能完全不同&#xff0c;也就是说&#xff0c;有多种状态&#xff0c;这种情况就叫做多态。 以下的例子里&#xff0c;同样都是人吃饭这个方法&#xff0c;但是不同的人使用吃…

【SpringBoot框架篇】36.整合Tess4J搭建提供图片文字识别的Web服务

文章目录 简介文件下载引入依赖main函数中使用基于Springboot搭建OCR Web服务配置traineddata路径枚举用到的语种类型定义接口响应的json数据格式封装OCR服务引擎编写web提供服务的接口启动服务并且测试html demo扩展 项目配套代码 简介 Tess4J是一个基于Tesseract OCR引擎的J…

网上超市系统|基于Springboot的网上超市系统设计与实现(源码+数据库+文档)

网上超市系统目录 目录 基于Springboot的网上超市系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、商品信息管理 2、用户管理 1、 商品信息 2、购物车 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、…

重要通告 | 公司更名为“浙江实在智能科技有限公司”

更名公告 升级蜕变、砥砺前行 因业务快速发展和战略升级&#xff0c;经相关政府机构批准&#xff0c;自2024年3月1日起&#xff0c;原“杭州实在智能科技有限公司”正式更名为“浙江实在智能科技有限公司”。 更名后&#xff0c;公司统一社会信用代码不变&#xff0c;业务主体…

力扣hot100:152.乘积最大子数组(动态规划)

一个子数组问题&#xff0c;我们要使用线性dp&#xff0c;最好先考虑以i结尾&#xff0c;如果定义dp[i]为前i个数最大子数组乘积值 那么dp[i-1]就无法转移到dp[i]。因此我们先考虑dp[i]定义为以第i个数结尾的最大子数组乘积值。 53. 最大子数组和 最大子数组和是一个动态规划问…

b树(一篇文章带你 理解 )

目录 一、引言 二、B树的基本定义 三、B树的性质与操作 1 查找操作 2 插入操作 3 删除操作 四、B树的应用场景 1 数据库索引 2 文件系统 3 网络路由表 五、哪些数据库系统不使用B树进行索引 1 列式数据库 2 图形数据库 3 内存数据库 4 NoSQL数据库 5 分布式数据…

yolov5体验

无须安装CUDA&#xff0c;只需要有NVIDIA图形驱动即可 1. 安装Miniconda miniconda下载地址 1.1 安装细节 一个对勾都不要选择 1.2 配置环境变量 在环境变量Path中添加如下变量 C:\Server\miniconda C:\Server\miniconda\Scripts C:\Server\miniconda\Library\bin2. …

不同路径 不同路径 II 整数拆分

62.不同路径 力扣题目链接(opens new window) 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。…