【Linux系统编程】第四十二弹---多线程编程全攻略:涵盖线程创建、异常处理、用途、进程对比及线程控制

 ✨个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】

目录

1、线程创建

2、线程异常

3、线程用途

4、进程 VS 线程

5、线程控制

5.1、创建和等待线程


1、线程创建

线程能看到进程的大部分资源,下面做一个对全局变量修改的测试验证!!!

代码演示 

int gval = 100;void* threadStart(void* args)
{// 新线程while(true){std::cout << "new thread running..." << ",pid: " << getpid()<< ",gval: " << gval << ",&gval: " << &gval << std::endl;sleep(1);}
}// 线程访问全局变量
int main()
{pthread_t tid;pthread_create(&tid,nullptr,threadStart,(void*)"thread-new");// 主线程while(true){std::cout << "main thread running..." << ",pid: " << getpid()<< ",gval: " << gval << ",&gval: " << &gval << std::endl;gval++; // 主线程修改全局变量sleep(1);}return 0;
}

运行结果  

2、线程异常

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

代码演示  

// 单个线程崩溃,会导致进程崩溃
int gval = 100;void *threadStart(void *args)
{// 新线程while (true){sleep(1);int x = rand() % 5; // 生成0-4的随机数std::cout << "new thread running..." << ",pid: " << getpid()<< ",gval: " << gval << ",&gval: " << &gval << std::endl;// 随机数等于0则让线程崩溃if (x == 0){int *p = nullptr; // 空指针解引用问题*p = 100;}}
}// 线程访问全局变量
int main()
{srand(time(nullptr));// 创建3个线程pthread_t tid1;pthread_create(&tid1, nullptr, threadStart, (void *)"thread-new1");pthread_t tid2;pthread_create(&tid2, nullptr, threadStart, (void *)"thread-new2");pthread_t tid3;pthread_create(&tid3, nullptr, threadStart, (void *)"thread-new3");// 主线程while (true){std::cout << "main thread running..." << ",pid: " << getpid()<< ",gval: " << gval << ",&gval: " << &gval << std::endl;gval++; // 主线程修改全局变量sleep(1);}return 0;
}

运行结果 

3、线程用途

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

4、进程 VS 线程

  • 进程是资源分配的基本单位
  • 线程是调度的基本单位
  • 线程共享进程数据,但也拥有自己的一部分数据:
    • 线程ID
    • 一组寄存器(保存硬件上下文数据)
    • 栈(程序在运行的时候,会形成各种临时变量,临时变量被每个线程保存在自己的栈区)
    • errno
    • 信号屏蔽字
    • 调度优先级


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

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

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

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

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

线程调度成本为什么比进程更低?(面试题)

一、进程与线程的基本概念

  1. 进程:进程是资源分配的最小单位,每个进程都有自己独立的地址空间,系统需要为进程分配地址空间并建立数据表来维护其代码段、堆栈段和数据段。这种操作相对复杂且开销较大。
  2. 线程:线程是程序执行的最小单位(资源调度的最小单位),它是进程的一部分,共享进程所拥有的资源。因此,线程切换时无需像进程切换那样重新分配地址空间和维护数据表,从而减少了开销。

二、上下文切换的开销

  1. CPU上下文切换:无论是进程调度还是线程调度,都需要进行CPU上下文切换。这部分开销在两者中是相似的。
  2. CPU Cache/TLB命中率线程切换时,由于多个线程共享进程的地址空间,因此CPU Cache(高速缓存)和TLB(转换后备缓冲器)中的内容在切换后仍然有效,命中率较高。而进程切换时,由于地址空间的变化,原有的Cache和TLB内容可能不再适用,导致命中率下降,触发更多的缺页中断,从而增加了开销。

三、资源共享与通信

  1. 资源共享:线程共享进程的资源,包括地址空间、全局变量、静态变量等。这使得线程之间的通信更加便捷,无需像进程间通信那样通过IPC(进程间通信)方式进行,从而减少了通信开销。
  2. 通信开销进程间通信需要借助额外的机制(如管道、信号、共享内存等),这些机制的实现和维护都会增加开销。而线程间通信则可以直接通过共享内存进行,无需额外的通信机制。

四、创建与销毁的开销

  1. 创建开销:由于进程需要分配独立的地址空间和维护数据表,因此创建进程的开销相对较大。而线程则共享进程的地址空间,无需进行这些操作,因此创建线程的开销较小。
  2. 销毁开销:同样地,由于进程拥有独立的资源,因此在销毁时需要释放这些资源,开销较大。而线程则无需释放独立的资源,销毁开销相对较小。

5、线程控制

线程控制:创建,终止,等待,分离

POSIX线程库

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

5.1、创建和等待线程

pthread_join()

pthread_join - 等待指定的线程终止#include <pthread.h>int pthread_join(pthread_t thread, void **retval);

参数:

  • pthread_t thread:这是你想要等待的线程的标识符。线程标识符是在创建线程时通过 pthread_create 函数返回的。
  • void **retval:这是一个指向指针的指针(二级指针),用于接收被等待线程的返回值。如果你不需要获取线程的返回值,可以将这个参数设置为 nullptr。被等待线程的返回值应该是一个 void* 类型的指针,在调用 pthread_exit 或从线程的启动函数返回时设置。

返回值:

  • 成功时pthread_join 返回 0
  • 失败时,返回一个错误码。常见的错误码包括:
    • ESRCH:指定的线程不存在。
    • EINVAL:线程不是可连接的(即,线程不是可加入的,可能因为它已经终止了,或者它是以分离状态创建的)。
    • EDEADLK:检测到死锁(在尝试加入一个已经由调用线程加入的线程时可能发生)。
    • 其他可能的错误码,具体取决于系统实现。

代码演示

新线程执行函数

void *threadRun(void *args)
{int cnt = 10;while(cnt){// 每隔一秒打印一次std::cout << "new thread run...,cnt: " << cnt-- << std::endl;sleep(1);}return nullptr;
}

主函数

int main()
{pthread_t tid;// 创建新线程int n = pthread_create(&tid, nullptr, threadRun, (void *)"thread 1");if (n != 0) // 后面暂时不关心{std::cerr << "create thread errno " << std::endl;return 1;}std::cout << "main thread join begin..." << std::endl;// 等待新线程终止n = pthread_join(tid,nullptr); if(n == 0){std::cout << "main thread wait success " << std::endl;}return 0;
}

运行结果 

问题1 : main 和 new 线程谁先运行?

不确定

问题2 : 我们期望谁最后退出?

main thread最后退出,类似与父进程最后退出,回收子进程 , 你如何保证呢?

  • join来保证不join呢?
    • 主线程活着,新线程退出会造成类似僵尸问题

问题3 :tid是什么样子的?是什么呢?

tid通过10进制打印是一个很大的值,tid实际上是一个虚拟地址,可以通过16进制进行打印。

打印函数

// 10进制打印tid
void PrintToDec(pthread_t &tid)
{std::cout << "tid: " << tid << std::endl; 
}// 16进制打印tid
std::string PrintToHex(pthread_t &tid)
{char buffer[128];snprintf(buffer,sizeof(buffer),"0x%lx",tid);return buffer;
}

主函数 

int main()
{pthread_t tid;// 创建新线程int n = pthread_create(&tid, nullptr, threadRun, (void *)"thread 1");// 问题3 : tid是什么样子的?是什么呢?虚拟地址! 为什么?PrintToDec(tid); // 按照10进制方式打印std::string tid_str = PrintToHex(tid); // 按照16进制方式打印std::cout << "tid: " << tid_str << std::endl;std::cout << "main thread join begin..." << std::endl;// 等待新线程终止n = pthread_join(tid,nullptr); if(n == 0){std::cout << "main thread wait success " << std::endl;}return 0;
}

运行结果 

问题4 : 全面看待线程函数传参?

我们可以传递任意类型,但你一定要能想得起来,也能传递类对象地址!! 

方式一:传字符串常量

代码演示 

void *threadRun(void *args)
{std::string name = (const char*)args;int cnt = 10;while(cnt){// 每隔一秒打印一次std::cout << name << " run...,cnt: " << cnt-- << std::endl;sleep(1);}return nullptr;
}
int main()
{pthread_t tid;// 创建新线程int n = pthread_create(&tid, nullptr, threadRun, (void *)"thread 1");std::string tid_str = PrintToHex(tid); // 按照16进制方式打印出来std::cout << "tid: " << tid_str << std::endl;std::cout << "main thread join begin..." << std::endl;// 等待新线程终止n = pthread_join(tid,nullptr); if(n == 0){std::cout << "main thread wait success " << std::endl;}return 0;
}

运行结果 

方式二:传整数

代码演示 

void *threadRun(void *args)
{int a = *(int*)args;// warning 系统为64位,指针大小为8字节,int为4字节int cnt = 10;while(cnt){std::cout << a << " run...,cnt: " << cnt-- << std::endl;sleep(1);}return nullptr;
}
int main()
{pthread_t tid;int a = 100;int n = pthread_create(&tid, nullptr, threadRun, (void *)&a);std::string tid_str = PrintToHex(tid); // 按照16进制方式打印出来std::cout << "tid: " << tid_str << std::endl;std::cout << "main thread join begin..." << std::endl;// 等待新线程终止n = pthread_join(tid,nullptr); if(n == 0){std::cout << "main thread wait success " << std::endl;}return 0;
}

运行结果 

 方式二:传类对象

代码演示 

class ThreadData
{
public:std::string name;int num;
};
void *threadRun(void *args)
{ThreadData* td = static_cast<ThreadData*>(args); // 安全类别强转 (ThreadData*)argsint cnt = 10;while(cnt){std::cout << td->name << " run...,num is " << td->num << ",cnt: " << cnt-- << std::endl; sleep(1);}return nullptr;
}

主函数

int main()
{pthread_t tid;ThreadData td;td.name = "thread-1";td.num = 1;int n = pthread_create(&tid, nullptr, threadRun, (void*)&td); // 传递线程结构体对象std::string tid_str = PrintToHex(tid); // 按照16进制方式打印出来std::cout << "tid: " << tid_str << std::endl;std::cout << "main thread join begin..." << std::endl;// 等待新线程终止n = pthread_join(tid,nullptr); if(n == 0){std::cout << "main thread wait success " << std::endl;}return 0;
}

运行结果  

创建新线程访问栈上的空间不推荐,因为当多个新线程访问同一个结构体数据时,可能造成数据互相影响的问题,如果只读问题不大,但是如果一个线程对该数据进行修改,那么后面所有线程访问的数据都会修改!!!

// 再创建一个新线程,使用同一个局部变量,修改值两个都修改了
td.name = "thread-2";
td.num = 2;
n = pthread_create(&tid, nullptr, threadRun, (void*)&td); // 传递线程结构体对象

运行结果 

推荐在堆上申请空间,一个新线程申请一个类对象,使用完毕释放空间! 

void *threadRun(void *args)
{ThreadData* td = static_cast<ThreadData*>(args); // 安全类别强转 (ThreadData*)argsint cnt = 10;while(cnt){std::cout << td->name << " run...,num is " << td->num << ",cnt: " << cnt-- << std::endl; sleep(1);}std::cout << "delete td:" << td << std::endl;delete td; // 释放空间return nullptr;
}
int main()
{pthread_t tid;ThreadData* td = new ThreadData();td->name = "thread-1";td->num = 1;int n = pthread_create(&tid, nullptr, threadRun, td); std::string tid_str = PrintToHex(tid); // 按照16进制方式打印出来std::cout << "tid: " << tid_str << std::endl;std::cout << "main thread join begin..." << std::endl;// 等待新线程终止n = pthread_join(tid,nullptr); if(n == 0){std::cout << "main thread wait success " << std::endl;}return 0;
}

运行结果 

问题5: 全面看待线程函数返回:? 

新线程函数返回值

1、只考虑正确的返回,不考虑异常,因为异常了,整个进程就崩溃了,包括主线程。

新线程通过函数返回值给主线程!!!

代码演示  

void *threadRun(void *args)
{ThreadData* td = static_cast<ThreadData*>(args); // 安全类别强转 (ThreadData*)argsint cnt = 10;while(cnt){std::cout << td->name << " run...,num is " << td->num << ",cnt: " << cnt-- << std::endl; // int* p = nullptr;// *p = 100; // 故意野指针sleep(1);}std::cout << "delete td:" << td << std::endl;delete td; // 释放空间return (void*)111;
}

主线程获取新线程的返回值信息!!! 

int main()
{pthread_t tid;ThreadData* td = new ThreadData();td->name = "thread-1";td->num = 1;int n = pthread_create(&tid, nullptr, threadRun, td); std::cout << "main thread join begin..." << std::endl;// 等待新线程终止void* code = nullptr; // 开辟了空间的!!!n = pthread_join(tid,&code); if(n == 0){// 主线程拿新线程的退出信息,int会有精度损失,Linux中地址8字节,int4字节std::cout << "main thread wait success, new thread exit code: " << (uint64_t)code << std::endl;}return 0;
}

运行结果 

 新线程故意野指针!!!

运行结果  

2、我们可以传递任意类型,但你一定要能想得起来,也能传递类对象地址!! 

类对象

class ThreadData
{
public:int Excute(){return x + y;}
public:std::string name;int x;int y;
};class ThreadResult
{
public:std::string Print(){return std::to_string(x) + "+" + std::to_string(y) + "=" + std::to_string(result);}
public:int x;int y;int result;
};

新线程函数

void *threadRun(void *args)
{ThreadData* td = static_cast<ThreadData*>(args); int cnt = 10;ThreadResult* result = new ThreadResult();while(cnt){sleep(3);std::cout << td->name << " run...,cnt: " << cnt-- << std::endl; result->result = td->Excute();result->x = td->x;result->y = td->y;break;}std::cout << "delete td:" << td << std::endl;delete td; // 释放空间return (void*)result;
}

主函数

int main()
{pthread_t tid;ThreadData* td = new ThreadData();td->name = "thread-1";td->x = 10;td->y = 20;int n = pthread_create(&tid, nullptr, threadRun, td); std::cout << "main thread join begin..." << std::endl;// 等待新线程终止ThreadResult* result = nullptr; // 开辟了空间的!!!n = pthread_join(tid,(void**)&result); if(n == 0){std::cout << "main thread wait success, new thread exit code: " << result->Print() << std::endl;}return 0;
}

运行结果 

问题6 : 如何创建多线程呢? 

错误示范(X)

在for循环内部创建临时变量!!!

代码演示  

const int num = 10;void *threadrun(void *args)
{std::string name = static_cast<const char *>(args);while (true){// 打印的线程名是乱的,线程执行顺序是不确定的,// 且因为在名字栈区for循环内部创建,每切换一个线程,名字就会被覆盖,有问题!!!std::cout << name << " is running" << std::endl;sleep(1);}return nullptr;
}
int main()
{// 问题6 : 如何创建多线程呢?std::vector<pthread_t> tids;for (int i = 0; i < num; i++){// 1.有线程的idpthread_t tid;// 2.有线程的名字char name[128];snprintf(name, sizeof(name), "thread-%d", i + 1);pthread_create(&tid, nullptr, threadrun, /*线程的名字*/ name);}// join todosleep(100);return 0;
}

运行结果  

正确示范

只需让name在堆区创建即可,并修改格式化name函数

// 2.有线程的名字(正确示范)
char* name  = new char[128];
snprintf(name, 128, "thread-%d", i + 1);

等待(终止)多线程

创建好新线程之后,保存每个线程的tid,遍历vector终止新线程即可!

代码演示  

const int num = 10;void *threadrun(void *args)
{std::string name = static_cast<const char *>(args);while (true){// 打印的线程名是乱的,线程执行顺序是不确定的,// 且因为在名字栈区for循环内部创建,每切换一个线程,名字就会被覆盖,有问题!!!std::cout << name << " is running" << std::endl;sleep(1);break;}// return nullptr;return args;
}
int main()
{// 问题6 : 如何创建多线程呢?std::vector<pthread_t> tids;for (int i = 0; i < num; i++){// 1.有线程的idpthread_t tid;// 2.有线程的名字(错误示范)// char name[128];// snprintf(name, sizeof(name), "thread-%d", i + 1);// 2.有线程的名字(正确示范)char* name  = new char[128];snprintf(name, 128, "thread-%d", i + 1);pthread_create(&tid, nullptr, threadrun, /*线程的名字*/ name);// 3.保存所有线程的id信息tids.emplace_back(tid);}// join todofor(auto tid : tids){void* name = nullptr;pthread_join(tid,&name);// std::cout << PrintToHex(tid) << " quit" << std::endl;std::cout << (const char*)name << " quit" << std::endl;delete (const char*)name;}// sleep(100);return 0;
}

tid方式打印

运行结果  

线程名方式打印

运行结果  

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

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

相关文章

基于SSM的在线作业管理系统 -octopus-master(源码+调试)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。你想解决的问题&#xff0c;今天给大家介绍…

医学影像类和医用电气设备测试标准整理

医学影像类和医用电气设备测试标准整理 1、GB 9706.225-2022 医用电气设备 第2-25部分:心电图机的基本安全和基本性能专用要求 GB 9706.225规定了在201.3.63中定义的通过自身或作为ME系统一部分,提供可供诊断用的心电图报告的心电图机基本安全和基本性能,以下称为ME设备。 …

鸿蒙原生应用开发及部署:首选华为云,开启HarmonyOS NEXT App新纪元

目录 前言 HarmonyOS NEXT&#xff1a;下一代操作系统的愿景 1、核心特性和优势 2、如何推动应用生态的发展 3、对开发者和用户的影响 华为云服务在鸿蒙原生应用开发中的作用 1、华为云ECS C系列实例 &#xff08;1&#xff09;全维度性能升级 &#xff08;2&#xff…

3^100的位数判断

3^100的位数判断 问题来源 字节面试&#xff0c;面试官提问&#xff1a;口算估计3^100的位数&#xff0c;或是给出位数估计范围。 解决方案 方法一&#xff1a; 该方法纯口算&#xff0c;可得一个较为准确的一个范围 2 100 < 3 100 < 4 100 2^{100}<3^{100}<…

ROS2简介与Ubuntu24.04中安装指南

之前安装了一个版本&#xff0c;但是不愿意写blog&#xff0c;现在想想自己就是个沙子立个flag&#xff0c;每次配置项目&#xff0c;写流程blog ROS简介 ROS&#xff08;Robot Operating System&#xff09;是一个开源的机器人软件平台&#xff0c;提供了许多工具和库来帮助…

Linux sudo命令及权限设置

普通用户的权限是有限制的&#xff0c;需要更大的权限&#xff0c;就需要使用 root 用户&#xff0c;但又不想一直使用 root 用户&#xff0c;如普通用户查看 8080 端口的监听情况&#xff1a; netstat -tulnp | grep :8080 只能查看自己的 不想用 root 用户&#xff0c;继续…

微服务网关的认证管理;原理与实践

API安全认证是网关的最重要能力 API 网关为了保护对外提供的API&#xff0c;避免诸如恶意访问、未授权访问、应用漏洞及黑客攻击等导致的数据和资产损失&#xff0c;采用API网关的认证机制显得十分必要。 这种认证机制通过基于token的身份验证来实现&#xff0c;它允许应用程…

STM32 + CubeMX + 硬件SPI + W5500 +TcpClient

这篇文章记录一下STM32W5500TCP_Client的调试过程&#xff0c;实现TCP客户端数据的接收与发送。 目录 一、W5500模块介绍二、Stm32CubeMx配置三、Keil代码编写1、添加W5500驱动代码到工程&#xff08;添加方法不赘述&#xff0c;驱动代码可以在官网找&#xff09;2、在工程中增…

微信小程序中,点击视频,没有跳转播放,可能是因为没有在app.json中正确注册视频播放页面的路径

const customMethodMap {handlePreview(e) {const { item: { url } } e?.currentTarget?.datasetconsole.log(Clicked item URL:, url); // 输出URLconst type url.split(.)[url.split(.)?.length - 1]console.log(File type:, type); // 输出文件类型console.log(isDoc(…

软件体系结构

第一章 构件 具有某种功能的 可复用的软件结构单元,为组装服务,可部署,具有规范的接口规约和显式的语境依赖 构件模型 构件模型是对构件本质特征的抽象描述&#xff0c;可以把它想象成一个类的组合&#xff0c;它封装了多个类&#xff0c;并具有一个或多个服务而提供了简单…

Spark 的Standalone集群环境安装与测试

目录 一、Standalone 集群环境安装 &#xff08;一&#xff09;理解 Standalone 集群架构 &#xff08;二&#xff09;Standalone 集群部署 二、打开监控界面 &#xff08;一&#xff09;master监控界面 &#xff08;二&#xff09;日志服务监控界面 三、集群的测试 &a…

react的antd-mobile使用Steps显示物流

antd-mobile的图标&#xff0c;是需要安装依赖的 step如果只有一个步骤是不会展示的&#xff0c;代码里面的标题那块可以看出来 尝试了很多遍测试发现一直不显示&#xff0c;查询后发现是这个组件的本身设置的原因 那么就算你只展示一个那么也要写两个step&#xff0c;第二个…

基于鸟类AI识别的果园智能物联网解决方案

1. 项目背景 我国拥有广阔的果园种植面积&#xff0c;但每年因鸟类造成的损失高达数亿元。传统的防鸟害措施&#xff0c;如建立防护网和使用物理化学方法&#xff0c;效果并不理想&#xff0c;且成本较高。为了解决这一问题&#xff0c;深圳快瞳科技有限公司的提出基于鸟类AI识…

让Erupt框架支持.vue文件做自定义页面模版

Erupt是什么&#xff1f; Erupt 是一个低代码 全栈类 框架&#xff0c;它使用 Java 注解 动态生成页面以及增、删、改、查、权限控制等后台功能。 零前端代码、零 CURD、自动建表&#xff0c;仅需 一个类文件 简洁的注解配置&#xff0c;快速开发企业级 Admin 管理后台。 提…

如何优雅处理异常?处理异常的原则

前言 在我们日常工作中&#xff0c;经常会遇到一些异常&#xff0c;比如&#xff1a;NullPointerException、NumberFormatException、ClassCastException等等。 那么问题来了&#xff0c;我们该如何处理异常&#xff0c;让代码变得更优雅呢&#xff1f; 1 不要忽略异常 不知…

DBAPI连接阿里云 maxcompute 报错

使用正确的驱动包 访问以下链接寻找驱动包 https://github.com/aliyun/aliyun-odps-jdbc/releases/tag/v3.4.3 注意要使用odps-jdbc-3.4.3-jar-with-dependencies.jar &#xff0c;这个是完整的jar包 不要使用odps-jdbc-3.4.3.jar&#xff0c;这个不是完整的&#xff0c;它还…

2024最新Python安装教程+Pycharm安装教程【附安装包】

Python安装 1.首先下载好Python安装包 获取方式&#xff1a;点击这里&#xff08;扫描神秘②薇码免下载&#xff09;完全免费&#xff01;&#xff01;&#xff01; 2.打开安装包&#xff0c;先勾选最下面两个选项&#xff0c;再选择第二个自定义安装 3.这里默认全选&#xff…

【数据库】elasticsearch

1、架构 es会为每个索引创建一定数量的主分片和副本分片。 分片&#xff08;Shard&#xff09;&#xff1a; 将索引数据分割成多个部分&#xff0c;每个部分都是一个独立的索引。 主要目的是实现数据的分布式存储和并行处理&#xff0c;从而提高系统的扩展性和性能。 在创建索…

JAVA基础:数组 (习题笔记)

一&#xff0c;编码题 1&#xff0c;数组查找操作&#xff1a;定义一个长度为10 的一维字符串数组&#xff0c;在每一个元素存放一个单词&#xff1b;然后运行时从命令行输入一个单词&#xff0c;程序判断数组是否包含有这个单词&#xff0c;包含这个单词就打印出“Yes”&…

【学习】使用webpack搭建react项目

前言 在日常工作中&#xff0c;我大多是在已有的项目基础上进行开发&#xff0c;而非从头构建项目。因此&#xff0c;我期望通过本次学习能填补我在项目初始化阶段知识的空白&#xff0c;与大家共同进步。在此过程中&#xff0c;我欢迎并感激任何指正或建议&#xff0c;无论是…