【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
进程和线程的关系如下图:

如何看待之前学习的单进程?
具有一个线程执行流的进程 。
线程调度成本为什么比进程更低?(面试题)
一、进程与线程的基本概念
- 进程:进程是资源分配的最小单位,每个进程都有自己独立的地址空间,系统需要为进程分配地址空间并建立数据表来维护其代码段、堆栈段和数据段。这种操作相对复杂且开销较大。
- 线程:线程是程序执行的最小单位(资源调度的最小单位),它是进程的一部分,共享进程所拥有的资源。因此,线程切换时无需像进程切换那样重新分配地址空间和维护数据表,从而减少了开销。
二、上下文切换的开销
- CPU上下文切换:无论是进程调度还是线程调度,都需要进行CPU上下文切换。这部分开销在两者中是相似的。
- CPU Cache/TLB命中率:线程切换时,由于多个线程共享进程的地址空间,因此CPU Cache(高速缓存)和TLB(转换后备缓冲器)中的内容在切换后仍然有效,命中率较高。而进程切换时,由于地址空间的变化,原有的Cache和TLB内容可能不再适用,导致命中率下降,触发更多的缺页中断,从而增加了开销。
三、资源共享与通信
- 资源共享:线程共享进程的资源,包括地址空间、全局变量、静态变量等。这使得线程之间的通信更加便捷,无需像进程间通信那样通过IPC(进程间通信)方式进行,从而减少了通信开销。
- 通信开销:进程间通信需要借助额外的机制(如管道、信号、共享内存等),这些机制的实现和维护都会增加开销。而线程间通信则可以直接通过共享内存进行,无需额外的通信机制。
四、创建与销毁的开销
- 创建开销:由于进程需要分配独立的地址空间和维护数据表,因此创建进程的开销相对较大。而线程则共享进程的地址空间,无需进行这些操作,因此创建线程的开销较小。
- 销毁开销:同样地,由于进程拥有独立的资源,因此在销毁时需要释放这些资源,开销较大。而线程则无需释放独立的资源,销毁开销相对较小。
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方式打印
运行结果

线程名方式打印
运行结果

相关文章:
【Linux系统编程】第四十二弹---多线程编程全攻略:涵盖线程创建、异常处理、用途、进程对比及线程控制
✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、线程创建 2、线程异常 3、线程用途 4、进程 VS 线程 5、线程控制 5.1、创建和等待线程 1、线程创建 线程能看到进程的大…...
Rust 力扣 - 2379. 得到 K 个黑块的最少涂色次数
文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 本题可以转换为求长度为k的子数组中白色块的最少数量 我们遍历长度为k的窗口,我们只需要记录窗口内的白色块的数量即可,遍历过程中刷新白色块的数量的最小值 题解代码 impl Solution {…...
“单元测试”应该怎么写比较好
如何正确写单元测试 单元测试重要性写单元测试时存在的问题1、如何命名测试类&方法1.1、测试类命名规范1.2、测试方法命名规范 2、测试类的要求2.1测试行覆盖率100%2.2、单一职责2.3、可重复2.4、外部隔离,无任何外部依赖2.5、正确的使用断言2.6、不应该为了测试…...
腾讯开源首个文图生3D大模型Hunyuan3D-1.0
🦉 AI新闻 🚀 腾讯开源首个文图生3D大模型Hunyuan3D-1.0 摘要:腾讯混元于11月5日开源了Hunyuan3D-1.0,这是首个同时支持文本生成和图像生成的3D开源大模型。该模型采用两阶段生成方法,可以在10秒内生成3D资产。第一阶…...
c语言简单编程练习10
1、typedef和#define的区别 在用作数据类型替换时的区别: #include <stdio.h> #include <unistd.h>typedef char * A; //typedef需要; #define B char *int main(int argc, char *argv[]) {A a,b;B c,d;printf("a_size%ld\n"…...
时序预测 | Matlab基于TSA-LSTM-Attention被囊群优化算法优化长短期记忆网络融合注意力机制多变量多步时间序列预测
时序预测 | Matlab基于TSA-LSTM-Attention多变量多步预测 目录 时序预测 | Matlab基于TSA-LSTM-Attention多变量多步预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 时序预测 | Matlab基于TSA-LSTM-Attention被囊群优化算法优化长短期记忆网络融合注意力机制多变量多…...
数据结构[2016]
一、设有二维数组A[6][8],每个元素占6个字节存储,实现存放,A[0][0]的起始地址为1000,计算: (10分) (1)数组最后一个元素A[5][7]的起始地址; (2)按行优先存放时,元素A[1][4]的起始地址; (3)按列优先存放时…...
DBAPI连接阿里云 maxcompute 报错
使用正确的驱动包 访问以下链接寻找驱动包 https://github.com/aliyun/aliyun-odps-jdbc/releases/tag/v3.4.3 注意要使用odps-jdbc-3.4.3-jar-with-dependencies.jar ,这个是完整的jar包 不要使用odps-jdbc-3.4.3.jar,这个不是完整的,它还…...
Web3对社交媒体的影响:重新定义用户互动方式
随着互联网的发展和人们对隐私、安全、所有权的需求不断提高,Web3 的概念逐渐深入人心。Web3 的出现标志着一个去中心化、用户主导的网络时代的到来,这也将对社交媒体产生深远的影响。Web3 不仅推动社交媒体从中心化模式向用户主导的去中心化模式转变&am…...
【LeetCode】【算法】322. 零钱兑换
LeetCode 322. 零钱兑换 题目 给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回-1。 你可以认为每…...
人工智能技术:未来生活的“魔法师”
想象一下,未来的某一天,你醒来时,智能助手已经为你准备好了早餐,你的智能家居系统根据你的心情和日程安排调整了室内的光线和音乐,而你的自动驾驶汽车已经在门口等你。这不是科幻小说,这是人工智能技术为我…...
docker加载目录中所有的镜像
docker加载目录中所有的镜像 首先我们知道读取单个命令如下: docker load -i example_image.tar.gz读取两三个也是: docker load -i image1.tar.gz image2.tar.gz image3.tar.gz但是如果是几十个,那么上面的命令就显得捉襟见肘了;比如当前我有个image…...
使用免费的飞书机器人,实现消息推送实时通知
大家好,我是小悟。 实际工作中,我们会经常遇到需要给用户发送业务通知的功能需求,如果是小程序端,那么就使用小程序提供的模板消息通知,如果是APP端,一般就是使用个推、极光等第三方平台。 当然还有个万能…...
各种网络设备的工作原理
网络设备的工作原理涉及多种设备,包括路由器、交换机、防火墙等,它们各自承担着不同的功能。以下是对这些设备工作原理的详细解释: 一、路由器路由器是互联网通信中的关键设备,它负责在不同网络之间传输数据包。功能:路…...
FilterListener组件
文章目录 Java Web三大组件一、Filter概述二、Filter开始1_过滤器API介绍2_过滤器开发步骤3_代码实现4_过滤器执行流程小结 三、使用细节1_生命周期2_拦截路径3_过滤器链 四、Listener1_Listener概述2_监听器举例3_Listener开始4_案例:模拟spring框架 Java Web三大组件 组件: 是…...
使用Ubuntu快速部署MinIO对象存储
想拥有自己的私有云存储,安全可靠又高效?MinIO是你的理想选择!这篇文章将手把手教你如何在Ubuntu 22.04服务器上部署MinIO,并使用Nginx反向代理和Let’s Encrypt证书进行安全加固。 即使你是新手,也能轻松完成…...
基于Liquid State Machine的时间序列预测:利用储备池计算实现高效建模
Liquid State Machine (LSM) 是一种 脉冲神经网络 (Spiking Neural Network, SNN) ,在计算神经科学和机器学习领域中得到广泛应用,特别适用于处理 时变或动态数据。它是受大脑自然信息处理过程启发而提出的一种 脉冲神经网络 。 设想你正处于一片平静的湖面,四周环绕着高山,你向…...
oracle使用CTE递归分解字符串
oracle使用CTE递归分解字符串 背景 给定一个不定长度字符串 并且以,分割例如 ‘1,2,3,4’ 使用sql查询 返回1,2,3,4四行 如果‘1,2’ 则返回 1,2 两行 使用sql实现 实…...
华为HarmonyOS借助AR引擎帮助应用实现虚拟与现实交互的能力5-识别平面语义
对于检测到的平面,您可以通过AR Engine识别该平面的语义,包括墙面、地面、座椅面、桌面、天花板、门面、窗面、床面。 创建AR会话 创建AR会话并配置为平面语义识别模式。 AREngine_ARSession *arSession nullptr;// 创建AR会话。HMS_AREngine_ARSessi…...
MAC 安装 brew及其常用命令
文章:Mac安装brew的四种方法(指定能行) 以下是在 Mac 上使用 Homebrew 清理缓存和无用包的详细指南: 1. 查看系统状态 # 诊断系统问题 brew doctor# 查看已安装的包 brew list# 查看系统占用空间 brew cleanup -n # 预览需要…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...
NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...
MySQL的pymysql操作
本章是MySQL的最后一章,MySQL到此完结,下一站Hadoop!!! 这章很简单,完整代码在最后,详细讲解之前python课程里面也有,感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...
实战设计模式之模板方法模式
概述 模板方法模式定义了一个操作中的算法骨架,并将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的前提下,重新定义算法中的某些步骤。简单来说,就是在一个方法中定义了要执行的步骤顺序或算法框架,但允许子类…...
