【Linux】线程机制解析:理解、优势与Linux系统应用
文章目录
- 前言:
- 1. 线程概念
- 1.1. 什么是线程
- 1.2. 线程得优点:
- 1.3. 线程的缺点
- 线程异常
- 线程的用途
- 2. 线程的理解(Linux 系统为例)
- 2.1. 为什么要设计Linux“线程"?
- 2.2. 什么是进程?
- 2.3. 关于调度的问题
- 2.4. 再谈地址空间(页表、虚拟地址和物理地址)
- 4. 线程的控制
- 4.1. 线程的创建
- 4.2. 进程等待
- 4.3. 进程终止
- 4.4. 进程分离:
- 5. Linux进程 VS 线程
- 6. 在C++11 也带了多线程
- 总结:
前言:
在现代计算机系统中,多任务处理和并行计算的需求日益增长,这推动了线程技术的发展和应用。线程作为进程的一个执行单元,允许操作系统更高效地进行任务调度和管理。本文旨在深入探讨线程的概念、优势、缺点以及在Linux系统中的具体实现和控制方式。通过分析线程与进程的关系,以及C++11中多线程的支持,本文将为读者提供一个全面的线程技术概览。
1. 线程概念
线程是进程内部的一个执行分支,线程是CPU调度的基本单位
加载到内存中的程序,叫做进程。 修正:进程 = 内核数据结构 + 进程代码和数据
1.1. 什么是线程
- 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”
- 一切进程至少都有一个执行线程
- 线程在进程内部运行,本质是在进程地址空间内运行
- 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化
- 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流
1.2. 线程得优点:
- 创建一个新线程的代价要比创建一个新进程小得多
- 与进程之间的切换相比,线程 之间的切换需要操作系统做的工作要少很多
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
1.3. 线程的缺点
- 性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。 - 健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。 - 缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。编程难度提高编写与调试一个多线程程序比单线程程序困难得多
线程异常
- 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃。
- 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出。
线程的用途
- 合理的使用多线程,能提高CPU密集型程序的执行效率。
- 合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)
2. 线程的理解(Linux 系统为例)
正文:代码段(区),我们的代码在进程中,全部都属串行调用的!
进程创建,成本较高,时间和空间
地址空间和地址空间上的虚拟地址,本质是一种“资源”
2.1. 为什么要设计Linux“线程"?
如果我们要设计线程,OS也要对线程进行管理!先描述,再组织
Linux 的设计者认为,进程和线程都是执行流,具有极度的相似性,没有必要单独设计数据结构和算法,直接复用代码,使用进程来模拟线程!
以前的进程:一个内部只有一个线程的进程。
今天的进程:一个内部至少右一个线程的进程。
在现在来看,以前所学的进程,是今天的特殊情况。
2.2. 什么是进程?
进程的内核角度:承担分配系统资源的基本实体(不要站在调度的角度理解进程,而因该站在资源的角度理解进程)
2.3. 关于调度的问题
不用区分task_struct(进程?都是执行流!)
线程<=执行流(轻量级进程)<=进程
Linux中,所有的调度执行流,都叫做:轻量级进程。
2.4. 再谈地址空间(页表、虚拟地址和物理地址)
多个执行流是如何进行代码划分?如何理解?
- 操作系统要不要管理内存呢?
用4KB数据块
用页框或者页帧
struct Page
{int flag;// 其他属性
}
struct page mem[1048579]; // 对内存的管理就是对数组的增删查改!
给不同的线程分配表不同的区域,本质就是给让不同的线程,各自看到全部页表的子集!
4. 线程的控制
4.1. 线程的创建
// testThread.cc
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>void *newThreadRun(void *args)
{while (true){std::cout << "I am new thread,pid: " << getpid() <<std::endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, newThreadRun, nullptr); // 线程创建while (true) {std::cout << "I am main thread,pid: " << getpid() << std::endl;sleep(1);}return 0;
}
LWP:light weight process: 轻量级进程
所以,操作系统在进行调度的时候,用哪个id来进行调度呢?LWP
单进程,多进程? 每一个进程内部都只有一个执行流,LWP == PID
函数编译完成后,是若干行代码块,函数名是该代码块的入口地址。
最后形成的是一个可执行程序——所有的函数,都按照地址空间统一编址!
用户知道“轻量级进程”这个概念吗? 没有! 进程和线程。
将轻量级进程的系统调用进行封装,转成线程相关的接口语义提供给用户(pthread库——原生线程库,Linux系统自带,但不在内核,用户级线程)
所以Linux有没有真线程呢?没有,Linux 只有轻量级进程。
Linux 系统,不会有线程相关的系统调用,只有轻量级进程的系统调用。
#include <iostream>
#include <string>
#include <pthread.h> // 原生线程库的头文件
#include <unistd.h>
#include <sys/types.h>std::string ToHex(pthread_t tid)
{char id[64]; snprintf(id, sizeof(id), "0x%lx", tid);return id;
}void* newThreadRun(void* args)
{std::string threadname = (char*)args;int cnt = 5;while (cnt){std::cout << threadname << " is running " << cnt << ", pid:" << getpid() << ",mythread id:" << ToHex(pthread_self()) << std::endl;sleep(1);--cnt;}return nullptr;
}int main()
{ // 1. idpthread_t tid;pthread_create(&tid, nullptr, newThreadRun, (void*)"thread-1");// 2. 新和主两个线程,谁先运行呢?不确定,由调度器决定int cnt = 10;while (cnt) {std::cout << "I am main thread:" << cnt << ",pid: " <<getpid() << ",new thread id:" << ToHex(tid) << ",mainthread id:"<< ToHex(pthread_self()) << std::endl;sleep(1);--cnt;}return 0;
}
主进程与线程的id,都是可以获取的。
因为新旧进程的执行顺序是不确定的,所以开始两条打印时,会造成混再一起打印。
4.2. 进程等待
int n = pthread_join(tid, nullptr/*输出型参数*/); // 线程等待
#include <iostream>
#include <string>
#include <pthread.h> // 原生线程库的头文件
#include <unistd.h>
#include <sys/types.h>std::string ToHex(pthread_t tid)
{char id[64]; snprintf(id, sizeof(id), "0x%lx", tid);return id;
}void* newThreadRun(void* args)
{std::string threadname = (char*)args;int cnt = 5;while (cnt){std::cout << threadname << " is running " << cnt << ", pid:" << getpid() << ",mythread id:" << ToHex(pthread_self()) << std::endl;sleep(1);--cnt;}return nullptr;
}int main()
{ pthread_t tid;pthread_create(&tid, nullptr, newThreadRun, (void*)"thread-1");sleep(3);// 主线程退出 == 进程退出 == 所有线程都要退出// 1. 往往我们需要main thread最后结束// 2. 线程也要被“wait”,要不然会产生类似进程那里的内存泄 漏的问题 int n = pthread_join(tid, nullptr); // 线程等待std::cout << "main thread quit, n = " << n << std::endl; sleep(5);return 0;
}
4.3. 进程终止
return
pthread_exit
pathread_cancel
#include <iostream>
#include <string>
#include <pthread.h> // 原生线程库的头文件
#include <unistd.h>
#include <cstdlib>
#include <sys/types.h>// 同一个进程内的线程,大部分资源都是共享的,地址空间是共享的。
int g_val = 100;std::string ToHex(pthread_t tid)
{ char id[64]; snprintf(id, sizeof(id), "0x%lx", tid);return id;
}// 线程退出// 1. 代码跑完,结果对// 2. 代码跑完,结果不对// 3. 出异常了 —— 重点 —— 多线程中,任何一个线程出现异常(div 0, 野指针),都会导致整个进程退出。—— 多线程代码往往健壮性不好
void* newThreadRun(void* args)
{std::string threadname = (char*)args;int cnt = 5;while (cnt){std::cout << threadname << " is running " << cnt << ", pid:" << getpid() << ",mythread id:" << ToHex(pthread_self()) << ",g_val: "<< g_val << ",&g_val: "<< &g_val << std::endl;++g_val;sleep(1);// int *p = nullptr;// *p == 100; //故意一个野指针--cnt;}// 1. 线程函数结束// 2. pthread_exit((void*)123);//exit(10); // 不能用exit终止线程,因为它是终止进程的。// return (void*)123; // 返回给退出信息,warning}int main()
{ // 1. idpthread_t tid;pthread_create(&tid, nullptr, newThreadRun, (void*)"thread-1");// // 在主线程中,你保证新的进程已经启动// sleep(2);// pthread_cancel(tid); // 取消线程, 线程返回退出值-1.sleep(3);// 主线程退出 == 进程退出 == 所有线程都要退出// 1. 往往我们需要main thread最后结束// 2. 线程也要被“wait”,要不然会产生类似进程那里的内存泄 漏的问题 // // 2. 新和主两个线程,谁先运行呢?不确定,由调度器决定// int cnt = 10;// while (cnt) // {// std::cout << "I am main thread:" << cnt << ",pid: " <<getpid() // << ",new thread id:" << ToHex(tid) << ",mainthread id:"<< ToHex(pthread_self()) // << ",g_val: "<< g_val << ",&g_val: "<< &g_val << std::endl;// sleep(1);// --cnt;// }void* ret = nullptr;int n = pthread_join(tid, &ret); //我们怎么没有像进程一样获取线程的退出信号呢?只有你手动写的退出码// 不考虑线程的异常退出情况std::cout << "main thread quit, n = " << n << ",main thread get a ret: " << (long long)ret << std::endl; return 0;
}
与进程之间切换相比,线程之间得切换需要操作系统做的工作要少很多。
4.4. 进程分离:
进程分离通常是指将一个线程的生命周期从其创建者的控制中分离出来,使得线程成为一个独立运行的执行流。在多线程编程中,特别是在使用POSIX线程库(pthread)时,pthread_detach()
函数是用来实现线程分离的关键操作。
pthread_detach(tid);
5. Linux进程 VS 线程
- 进程是资源分配的基本单位
- 线程是调度的基本单位
- 线程共享进程数据,但也拥有自己的一部分数据:
- 线程ID
- 一组寄存器
- 栈
- errno
- 信号屏蔽字
- 调度优先级
进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
-
文件描述符表
-
每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
-
当前工作目录
-
用户id和组id
进程和线程的关系如下图:
-
线程私有:
- 线程的硬件上下文数据(CPU寄存器的值)(调度)
- 线程的独立栈结构(长规运行)
-
线程共享:
- 代码和全局数据
- 进程文件描述符表
1.一个线程出问题,导致其它线程也出问题,导致整个进程退出——线程安全问题
2.多线程中,公共函数如果被多个线程同时进入——该函数被重入。
6. 在C++11 也带了多线程
#include <iostream>
#include <thread> // C++
#include <vector>
#include <unistd.h>void threadrun(int num)
{while (num){std::cout << "I am a thread num: " << num << std::endl;sleep(1);}
}int main()
{ std::vector<std::thread> threads;int num_threads = 5;int thread_count = 10;for (int i = 0; i < num_threads; ++i) {threads.push_back(std::thread(threadrun, thread_count));}while (true){std::cout << "I am a main thread" << std::endl;sleep(1);}for (auto& t : threads) {t.join(); // 等待线程结束}return 0;
}
C++ 中的多线程,是对原生线程的封装。
1.为什么要做封装? 通过C++标准库,增加语言的跨平台
2.windows呢? 和Linux库不一样,不需要包含pthread库
3.其他语言呢? Linux提供多线程的底层的唯一方式
理解pthread:系统中没有线程,只有轻量级进程的概念
用户能不能通过接口,管理线程呢?比如创建,终止等待等。
线程的封装示例:
#ifndef __THREAD_HPP__
#define __THREAD_HPP__#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <pthread.h>namespace ThreadModule
{template<typename T>using func_t = std::function<void(T&)>;// typedef std::function<void(const T&)> func_t;template<typename T>class Thread{public:void Excute(){_func(_data);}public:Thread(func_t<T> func, T &data, const std::string &name="none-name"): _func(func), _data(data), _threadname(name), _stop(true){}static void *threadroutine(void *args) // 类成员函数,形参是有this指针的!!{Thread<T> *self = static_cast<Thread<T> *>(args);self->Excute();return nullptr;}bool Start(){int n = pthread_create(&_tid, nullptr, threadroutine, this);if(!n){_stop = false;return true;}else{return false;}}void Detach(){if(!_stop){pthread_detach(_tid);}}void Join(){if(!_stop){pthread_join(_tid, nullptr);}}std::string name(){return _threadname;}void Stop(){_stop = true;}~Thread() {}private:pthread_t _tid;std::string _threadname;T &_data; // 为了让所有的线程访问同一个全局变量func_t<T> _func;bool _stop;};
} // namespace ThreadModule#endif
总结:
本文全面介绍了线程的基础知识和在Linux系统中的应用。首先,我们定义了线程,并讨论了线程相比进程的优势,如资源占用少、创建和切换成本低,以及能够提高多处理器系统的并行计算能力。同时,也指出了线程的缺点,包括潜在的性能损失、健壮性降低和缺乏访问控制,这些缺点要求开发者在编写多线程程序时需要更加谨慎和深入的理解。
接着,文章以Linux系统为例,解释了线程的设计哲学,即利用进程的概念来模拟线程,这样做的好处是复用了现有的进程管理机制,减少了系统设计的复杂性。同时,我们也讨论了线程在内存管理、调度和控制方面的细节,包括线程的创建、等待、终止和分离等操作。
此外,本文还对比了Linux进程与线程的区别,指出了线程共享和私有的数据,以及线程安全和重入性问题。最后,文章介绍了C++11标准库对多线程的支持,展示了如何使用C++11的库来创建和管理线程,并提供了一个简单的线程封装示例,说明了C++多线程是对原生线程的高级封装,增强了跨平台的特性。
相关文章:

【Linux】线程机制解析:理解、优势与Linux系统应用
文章目录 前言:1. 线程概念1.1. 什么是线程1.2. 线程得优点:1.3. 线程的缺点线程异常线程的用途 2. 线程的理解(Linux 系统为例)2.1. 为什么要设计Linux“线程"?2.2. 什么是进程?2.3. 关于调度的问题2…...
java中简单工厂模式,工厂方法模式和抽象工厂模式的区别和联系?
在Java中,简单工厂模式、工厂方法模式和抽象工厂模式都是创建型设计模式,用于解耦对象的创建过程,提高系统的灵活性和可扩展性。它们之间既有相似之处也有明显的区别: 简单工厂模式(Simple Factory Pattern࿰…...
SERVER_DOWN 表示该服务器已经宕机或无法访问
[{"type":"MASTER","host":"/nodes/master/ds-master-0.ds-master-headless:5678","event":"SERVER_DOWN","warningLevel":"SERIOUS"}] 该JSON数据描述了一个事件通知,具体地&am…...

深度论证-高速走线控制100欧姆阻抗一定是最好的选择吗?
高速先生成员--黄刚 对于高速差分信号到底需要控制多少欧姆的阻抗,高速先生相信大部分工程师首先都会看下例如信号的协议文档或者芯片的文档,看看里面有没有推荐的控制阻抗值。例如像PCIE信号,在4.0之后的阻抗会明确要求按照85欧姆来控制&…...

【文末福利送资料】深度探索GPT模型,竟然10个字都不会说?
目录 导读 自回归模型 那么什么时候停下呢? 该停下来,但是概率不让啊 GPT欠缺的两种能力 目录 导读 自回归模型 那么什么时候停下呢? 该停下来,但是概率不让啊 GPT欠缺的两种能力 缺少规划 反省和修订 所有的人工智能…...

一些近来对内网攻防的思考
我知道我最近托更托了很久了,其实也不是小编懒啊 这小编也是一直在写,但是遇到的问题比较多(我太菜了),所以一直拖着。 但是总不能不更吧 那就讲一下进来的一些内网攻防的思考吧 1.CrossC2上线Linux到CS(成功) …...

数据结构--顺序表和链表的区别
顺序表和链表之间各有优劣,我们不能以偏概全,所以我们在使用时要关注任务的注重点,以此来确定我们要使用两者中的哪一个。 不同点: 存储空间上: 顺序表在物理结构上是一定连续的,而链表(这里以带头双向循环…...

【技术分享】 OPC UA安全策略证书简述
那什么是OPC UA证书?用途是什么? 简单来说它是身份验证和权限识别。 OPC UA使用X.509证书标准,该标准定义了标准的公钥格式。建立UA会话的时候,客户端和服务器应用程序会协商一个安全通信通道。数字证书(X.509&#x…...

【Neo4jJDK开箱即用的安装全流程】
neo4j:命令行本地访问loclhost neo4j:命令行本地访问loclhost2 neo4j操作 Neo4j桌面版数据库导出导入 Neo4j安装与配置以及JDK安装与配置教程(超详细) Neo4j 安装、使用教程 Neo4j安装教程 Neo4J桌面版的配置和连接Pycharm jdk-neo对应版本 JDK ORACLE中…...

Linux 操作系统多路IO复用
1.多路IO复用 多路I/O复用是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。 这个机制能够通过select/poll/eroll等来使用。这些函数都可以同时监视多…...
读取shape文件中数据
读取shape文件中数据 pom文件中引入依赖 <dependency><groupId>org.geotools</groupId><artifactId>gt-shapefile</artifactId><version>26.4</version></dependency>如果jar包下载不下来,在pom中添加下geotools的仓库 <…...

基于STM32F401RET6智能锁项目(使用库函数点灯、按键)
点灯硬件原理图 1、首先,我们查看一下原理图,找到相对应的GPIO口 LED_R低电平导通,LED4亮,所以LED_R的GPIO口需要配置一个低电平才能亮; LED_G低电平导通,LED3亮,所以LED_R的GPIO口需要配置一…...

网络层协议之 IP 协议
IP 协议格式 4 位版本:此处的取值只有两个,4(IPv4)和 6(IPv6),即指定 IP 协议的版本。 4 位首部长度:描述了 IP 报头多长,IP 报头是变长的,因为报头中的选项部…...

SpringBoot 实现对提供的接口进行 AES (加密,解密)
业务来源: 最近工作中,领导要求给别的项目组的小伙伴提供几个接口,要求对接口的参数进行解密操作,然后对返回的数据进行加密操作。 这时我想到了AES AES 是对称加密算法,优点:加密速度快;缺点…...

ASP.NET学生成绩管理系统
摘要 本系统依据开发要求主要应用于教育系统,完成对日常的教育工作中学生成绩档案的数字化管理。开发本系统可使学院教职员工减轻工作压力,比较系统地对教务、教学上的各项服务和信息进行管理,同时,可以减少劳动力的使用…...
谁考了第k名C++
题目描述 在一次考试中,每个学生的成绩都不相同,现知道了每个学生的学号和成绩,求考第k名学生的学号和成绩。(按成绩从大到小排列) 输入 第一行有两个整数,分别是学生的人数n(1≤n≤100&…...
多线服务器的优势有哪些?
服务器中包含着多线服务器、双线服务器和单线服务器等,其中多线服务器可以支持多个IP地址,今天小编就来带领大家一些来了解一下多线服务器的优势有哪些吧! 多线服务器是在一个互联网数据中心,通过特殊的技术手段将不同的多家网络接…...

vue + element-plus 开发中遇到的问题
1.问题之路由守卫 初写路由守卫,对于next()的理解不是很透彻,就想着都放行,不然看不到效果,结果控制台出现了警告,想着报黄的问题就不是问题,但仔细一看发现他说,如果再生产阶段就会失败&#x…...

使用java远程提交flink任务到yarn集群
使用java远程提交flink任务到yarn集群 背景 由于业务需要,使用命令行的方式提交flink任务比较麻烦,要么将后端任务部署到大数据集群,要么弄一个提交机,感觉都不是很离线。经过一些调研,发现可以实现远程的任务发布。…...

麻了!新增4.1分,CCF-C类,2区毕业神刊,被标记On Hold!
本周投稿推荐 SSCI • 2区社科类,3.0-4.0(社科均可) EI • 计算机工程类(接收广,录用极快) SCI&EI • 4区生物医学类,1.5-2.0(录用率99%) • 1区工程类&#…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...

Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...

selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...

接口自动化测试:HttpRunner基础
相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...