当前位置: 首页 > news >正文

linux线程,线程控制与线程相关概念

线程概念

线程这个词或多或少大家都听过,今天我们正式的来谈一下线程;

在我一开始的概念中线程就是进程的一部分,一个进程中有很多个线程,这个想法基本是正确的,但细节部分呢我们需要细细讲解一下;

什么是线程

1.线程是进程执行流中的一部分,就是说线程是进程内部的一个控制序列;

2.线程是操作系统调度的基本单位;

3.在linux中没有真正意义上的线程,也就是操作系统中说的tcb(thread ctrl block),但是其他的操作系统是有的不同的操作系统实现不同(如windows就是在pcb下再次构建了tcb的数据结构);为什么linux下没有真正意义的线程呢?因为线程再操作系统中也是需要被管理的,可是线程的管理一定得创建数据结构,创建复杂的数据结构一定需要增加维护的成本与难度,而线程的管理其实和进程是相似的;所以聪明的linux程序员将线程管理设计为了轻量化的进程,将线程与进程统一管理,减轻了代码的复杂度,便于维护提高效率;(线程粒度细于进程

4.线程其实是进程的一部分,所以线程运行的地方就是在进程的虚拟地址空间中的;因为线程本身也是属于进程的一部分的,只是被加载到了进程队列中运行而已;(进程就像是一个家庭,线程就像是家庭中的每一个人,每个人都有自己的工作,所以需要分开执行,也就是处于进程队列中),进程会分配的资源给线程(家庭中的资源会分配给每个人,比如爸爸要去远的地方工作需要开车,那车子这个资源就会分配给父亲),这个资源包括代码和数据,之前我们理解的进程可以当作是主线程,通过分配自己的代码给它内部的线程,内部的线程拿到数据和代码资源区执行分配给它的工作,从而执行相应的操作;

重谈虚拟地址空间

页表如何映射

计算页表大小

 所以一个页表最大为4mb,并且一个页表的二级页表不一定为1024个,因为页表的映射也不是一次就完成的,而已页表的映射使用完之后还会释放等;所以一个页表大小不会大于4mb;

就是这样的页表完成了我们的映射;那我们的数据和代码都是存储在这个地址空间上的;而函数就是一个现成的地址,所以我们分配给线程代码数据,是不是可以直接将这个函数分给线程呢?这样不就等于把线程需要执行的工作划分给了线程吗?

所以线程划分资源本质上是将地址空间中的资源进行分配

为什么我们要创建线程?线程优点

1.同一进程中线程之间的切换更加轻量化;

在我们的内存中最快的是寄存器,,cpu之间拿寄存器中的数据进行计算,寄存器也需要获取数据,而寄存器不是之间从内存中拿数据的,因为内存相较于寄存器还是太慢了,所以它们之间还有一个cache缓存,这个cache中存放的是当前进程的数据和指令,寄存器可以很快的就从cache中拿到一个进程中的数据(cache命中率会很高,因为都在同一进程,都是热数据);因为同一进程中的线程是共享数据的,所以cache切换时只需要切换task_struct,而进程之前切换所有数据都需要切换(进程切换了,进程间具有独立性,cache中的数据一定都需要被切换,所咦数据会变冷重新去命中数据),这样的切换消耗会大的多;

2.创建和销毁线程的代价要小很多;因为线程的数据已经在内存中了,线程只需要从它所在的进程中获取数据即可;

3.io密集型程序,通过多线程可以提高很大的效率,在进行io的时候进程可以让其他线程进行计算等操作,不需要等待io结束再操作;相比单线程的等待要优化非常多;

4.计算密集型程序,在单核cpu中多线程没有什么提升,想法,线程之间的切换还会降低效率;但是在多核cpu中,多线程可以在多个核上进行计算(计算线程数要小于等于核的数量),也是大大提高了计算的效率的;

线程缺点:

由于线程之前没有独立性,共享进程代码数据,代码的健壮性要低一些,所以需要进行同步于互斥;缺乏访问控制->健壮性低;相应的调试也会更难;

线程数据 

每个线程虽然都是进程的一部分,从进程中获得数据的,但是线程一定需要包含自己的数据;

线程自己的数据:

1.线程对应的上下文数据(寄存器)

2.线程运行时数据(独立的栈空间)

3.线程id

4.信号屏蔽字

5.调度优先级

 6.errno

线程操作

上面讲解了线程的基本内容,下面我们来对线程进行操作来理解线程;

我们需要先了解这些linux中posix标准中的原生线程库中的函数; 

线程创建

pthread_create

这个函数是用来创建子线程的;

第一个参数是一个输出型参数,用来输出创建线程的tid;

第二个参数是用来设置线程的属性的,其实这是一个指向线程属性对象的指针,通过传递我们设置好的对象传递给线程从而改变线程的默认属性,一般我们都传递NULL使用默认属性即可;

第三个参数是一个回调函数,用来提供给线程运行的代码,可以理解为让线程执行此函数;

第四个参数就是一个传递给函数(第三个参数——回调函数)的参数,这个参数既可以是普通的内置类型,也可以是结构体,这样可以很多的数据;

返回值返回0为成功创建,创建失败返回返回错误码,不设置errno;

 下面可以看到我们的代码成功运行了;

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
using namespace std;void *routine(void *data)
{for (int i = 0; i < 5; i++){usleep(100000);cout << "线程1, pid: " << getpid() << endl;}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, routine, nullptr);for (int i = 0; i < 7; i++){usleep(200000);cout << "线程0, pid: " << getpid() << endl;}return 0;
}

 从上面的现象我们可以清楚的知道线程是一个独立的执行流虽然routine函数和main函数它们两个再同一个程序中且是两个循环但是,这两个循环同时跑起来了,所以证明了线程的独立性;

编译时需要加-lpthread选项

在linux中使用原生线程库进行编程时我们编译选项总是需要带上-lpthread,这个选项在我们前面学习动静态库的时候就很熟悉了,用来连接指定的库;而似乎我们在以往的编程中除开我们自己创建动静态库的情况之外,我们从未出现过主动连接动静态库的情况;

为什么我们不需要主动去连接呢?这是因为编译器自动去帮我们连接了,我们的c,c++语言级别的库也好,linux的系统库也罢,它们库的路径都是已经存储在编译器的配置文件中的,编译器可以自动的找到库(第一步),然后编译器会自动连接这些库(第二步);为什么会自动连接呢?我们可以认为这些系统库和标准库是编译器自己的库,所以编译器会自动的连接;而pthread这个库是posix标准中的原生线程库;它是属于第三方库的,而第三方库即使它被放到系统,标准库的路径之下,它也是不会被自动连接的;所以我们需要带上-lpthread选项去主动连接这个库;

查看线程

我们看到的线程的现象接下来,我们从系统的角度的入手,使用系统的指令来查看我们的线程的体现;

ps -aL

lwp的全称是light weight process轻量级进程; 

线程的等待与tid获取函数

pthread_join

子进程被创建,父进程需要等待进程返回,而线程被创建也需要被等待,但是这里只有主线程和其他线程的区别,主线程需要等待其他所有线程,防止内存泄漏的问题;

 这里的第一个参数是指向被等待线程的tid;

第二个参数是一个输出型参数可以用来接收线程的返回值,这个返回值可以是任意类型的数据(自定义类型也可以);

返回值为0代表等待成功,非0则返回错误值,不设置errno码;

pthread_self

可以获得线程的tid;

这是一个无参函数和getpid的使用方式是一样的;

代码实现 

 知道了这些基本的函数后,我们下面用代码实践来展示现象并解释:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
using namespace std;struct thread_data
{string threadName;string threadReturn;
};void *routine(void *data)
{thread_data *d1 = static_cast<thread_data *>(data);// thread_data *d1 = (thread_data *)(data);int count = 3;for (int i = 0; i < count; i++){printf("tid: %p threadName: %s count: %d\n", pthread_self(), d1->threadName.c_str(), i);sleep(1);}// int a=5/0;//除0错误 这里说明了当进程中的某个线程出现异常时,整个进程都会退出// exit(0);//使用exit退出 这里也说明了使用exit会退出整个进程d1->threadReturn="return_"+d1->threadName;return d1;
}void initThread(thread_data *data, int num)
{data->threadName = "thread_" + to_string(num);
}int main()
{pthread_t tid;thread_data *data = new thread_data;initThread(data, 1);int ret_create = pthread_create(&tid, nullptr, routine, (void *)data);void *ret_thread;printf("我是主线程tid: %p\n",pthread_self());pthread_join(tid, &ret_thread);cout << ((thread_data *)ret_thread)->threadReturn << endl;//证明获得了一个类返回值delete data;return 0;
}

 使用return正常退出的情况:

下面是使用exit和异常退出的情况: 

 

通过代码和现象我们可以知道这些细节:

1. 我们可以使用join获取线程的返回值,线程返回值可以为任意类型的指针,所以可以传递任意值;

2.我们的子线程退出的时候不能使用exit退出这样会导致整个进程都退出,我们可以使用return,pthread_exit(后面讲),使用cacel取消joined(后面讲),这3种方式退出;

3.进程中的任意一个线程出现异常整个进程都会退出

4.线程的tid是一个地址,这个地址是进程堆栈之间的内存区域(通过上面的现象也可清楚的明白)

由此我们可以知道这些函数的大致使用;

线程结构体位置

上面我们通过概念与实现基本的了解了线程,接下来我们通过图像来了解线程的结构体:

其实我们的线程是这样存在在我们的进程中的,因为linux程序员为了减轻代码的维护效率linux中没有真正的线程,而是将线程作为轻量级进程,而用来描述轻量级进程的结构体是存储在用户层的,存储的位置就是共享区的原生线程库,线程库中维护了线程的属性数据,内核的执行流(tcb控制块)通过找到进程中的线程库中的线程结构体从而找到线程代码执行线程; 

所以线程的属性是由线程库来维护的,而tid之所以是共享区之中的代码的原因就是因为tid指的是共享区中线程库中的某个线程结构体所在的首地址;

线程空间的特点

1.线程之间的栈空间是独立的;

这一点非常好理解,因为函数在被调用的时候就会创建自己的栈帧嘛;而线程执行其实就是执行了分给他的函数;所以线程栈空间是独立的;

2.线程之间是没有秘密的;

为什么线程之间独立但是又没有秘密呢?因为线程总是在一个进程中的嘛,栈之间的数据,只需要通过一个指针就可以获得了;

代码示例:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <vector>
#include <string>
using namespace std;struct threadData
{string threadName;threadData(int num){threadName = "thread" + to_string(num);}threadData() = default;
};int *g_index;void *routine(void *args)
{int val = 0;threadData *data = (threadData *)args;for (int i = 1; i <=3 ; i++){printf("%s tid: %p val: %d\n", data->threadName.c_str(), pthread_self(), val);val++;}if(data->threadName=="thread1"){val=10000;g_index=&val;sleep(5);}return (void *)0;
}int main()
{vector<pthread_t> tids;for (int i = 0; i < 3; i++){pthread_t tid;threadData *td = new threadData(i);pthread_create(&tid, nullptr, routine, td);tids.push_back(tid);sleep(1);}cout<<"这是thread2的val值: "<<*g_index<<endl;for(auto t:tids){void *retData;pthread_join(t,&retData);}return 0;
}

但是如果我们想要获得某个栈空间的数据时这也是可以轻松做到的:

我们在routine函数中加入一段这样的代码,并在main函数中读取数据;

  routine函数中:if(data->threadName=="thread1"){val=10000;g_index=&val;sleep(5);}
main函数中:cout<<"这是thread2的val值: "<<*g_index<<endl;

 

线程的变量:__thread选项 

int *g_index;
//int g_val;
__thread int g_val;void *routine(void *args)
{int val = 0;threadData *data = (threadData *)args;for (int i = 1; i <=3 ; i++){//printf("%s tid: %p val: %d\n", data->threadName.c_str(), pthread_self(), val);//val++;printf("%s g_val: %d\n",data->threadName.c_str(),g_val);g_val++;}// if(data->threadName=="thread1")// {//     val=10000;//     g_index=&val;//     sleep(5);// }return (void *)0;
}int main()
{vector<pthread_t> tids;for (int i = 0; i < 3; i++){pthread_t tid;threadData *td = new threadData(i);pthread_create(&tid, nullptr, routine, td);tids.push_back(tid);sleep(1);}//cout<<"这是thread2的val值: "<<*g_index<<endl;for(auto t:tids){void *retData;pthread_join(t,&retData);}return 0;
}

我们线程在使用 g_val全局变量时:

g_val带上__thread编译选项时:

 

__thread是编译选择,不是c,c++的语法是编译器的选项;

特点:

 1.将进程全局数据变为线程全局数据

2.只能给内置类型带上这个选项

C++线程库说明

在我们的C++中是有语言级别的线程库的(C语言没有),C++中的线程库是跨平台的,但是我们在使用C++线程库时,我们还是会发现,我们需要带上编译选项-lpthread所以说明C++的线程库是封装了原生线程库的,而原生线程库在linux中是posix标准的,在windows中又有不同的标准;但是C++的线程库是跨平台的,所以说明C++的线程库不仅封装了linux的posix标准线程库还封装了windows下的线程库;

clone系统调用的封装

我们前面说线程是轻量级的进程,为什么这么说呢?其实我们在创建线程时使用的pthread_create函数和创建子进程的fork函数都是封装了clone的系统调用;

int clone(int (*fn)(void *), void *child_stack
, int flags, void *arg
, ... /* pid_t *ptid, void *tls, pid_t *ctid */);

这个系统系统调用会指定一片栈空间给新开辟的线程,我们不需要懂clone调用的细节,我们只需要知道,linux中其实在底层上线程的接口也是和进程用的一样的调用,所以它们在内核层面上是处于同一级别的执行流的,所以线程被称为轻量级进程;

小提示:

线程如何使用进程替换的调用会将当前的整个进程替换掉

线程终止

前面我们说了线程的3个正常退出方式;我们下面来详细的讲解一下:

pthread_exit

这个函数就是和return一样的作用,返回一个retval给主线程;这里需要注意的是retval最好是堆上的指针,线程终止栈帧也会销毁,会导致栈上的数据被释放,所以返回值一定要是不被释放的数据;

pthread_cancel

这是一个线程终止函数,我们可以通过此函数终止掉tid的线程:

这里终止了就不需要再join了,如果join了会发返回非0值; 

这是gpt给出的提示:

尽管 pthread_cancel 函数可以请求取消另一个线程,但是线程是否真正被取消,以及何时被取消,是由目标线程自身来决定的。目标线程可以选择忽略取消请求,或者在适当的时机响应取消请求并执行清理操作。

 线程分离

pthread_detach

我们的主线程永远是最后退出的,因为需要等待所有创建进程退出,我们常见的服务器一般都是死循环不退出的程序;而当主线程不关系创建的线程的结果时,可以使用detach来断开创建线程与主线程之间的关系;,主线程就不需要等待子线程了;

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include<cstring>
using namespace std;void* routine(void*args)
{cout<<"我是被创建线程"<<endl;
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,routine,nullptr);void* ret;pthread_detach(tid);int ret_join=pthread_join(tid,&ret);printf("%s\n",strerror(ret_join));return 0;
}

当没有detach时:

当创建的线程被detach时

 所以说明线程不能被同时detach和join;

此外线程可以自己detach自己;

以上就是线程的控制与基本概念,线程部分未完待续; 

相关文章:

linux线程,线程控制与线程相关概念

线程概念 线程这个词或多或少大家都听过&#xff0c;今天我们正式的来谈一下线程&#xff1b; 在我一开始的概念中线程就是进程的一部分&#xff0c;一个进程中有很多个线程&#xff0c;这个想法基本是正确的&#xff0c;但细节部分呢我们需要细细讲解一下&#xff1b; 什么…...

第八大奇迹

目录 题目描述 输入描述 输出描述 输入输出样例 示例 输入 输出 运行限制 原题链接 代码思路 题目描述 在一条 R 河流域&#xff0c;繁衍着一个古老的名族 Z。他们世代沿河而居&#xff0c;也在河边发展出了璀璨的文明。 Z 族在 R 河沿岸修建了很多建筑&#xff0c…...

MySQL:CRUD初阶(有图有实操)

文章目录 &#x1f4d1;1. 数据库的操作&#x1f324;️1.1 显示当前的数据库&#x1f324;️1.2 创建数据库&#x1f324;️1.3 选中数据库&#x1f324;️1.4 删除数据库 &#x1f4d1;2. 表的操作&#x1f324;️2.1 查看表结构&#x1f324;️2.2 创建表&#x1f324;️2.3…...

『大模型笔记』使用 vLLM 和 PagedAttention 快速提供 LLM 服务!

使用 vLLM 和 PagedAttention 快速提供 LLM 服务! 文章目录 一. 使用 vLLM 和 PagedAttention 快速提供 LLM 服务!1.1. PagedAttention二. 参考文献小红书中文字幕视频:https://www.xiaohongshu.com/explore/66502b60000000000500433e官网文档(推荐,里面有动图解释):vLLM:…...

简述vue-loader是什么?使用它的用途有哪些

vue-loader是一个webpack的加载器(loader)&#xff0c;主要用于将Vue组件的单文件(.vue文件)转换为JavaScript模块。使用vue-loader的主要用途包括&#xff1a; 解析.vue文件&#xff1a;vue-loader能够解析.vue文件中的模板、样式和脚本&#xff0c;并将它们分离出来进行处理…...

如何远程访问Redis?

远程访问Redis是一种常见的需求&#xff0c;特别是在分布式系统或跨地域网络中。通过远程访问&#xff0c;我们可以轻松地对远程的Redis数据库进行操作和管理。 天联保障数据安全 对于远程访问Redis的安全性问题&#xff0c;我们可以借助天联来保障数据的安全。天联是一种基于…...

#12松桑前端后花园周刊-SolidStart、Vercel融资、Angular18、Nextjs15RC、p5.js、ChromeDevTools引入AI

⚡️行业动态 SolidStart 1.0 元框架发布 Solidjs 核心团队发布其元框架 SolidStart 1.0 正式版&#xff0c;其特点如下&#xff1a;基于文件系统的路由&#xff1b;支持SSR、流式SSR、CSR、SSG渲染模式&#xff1b;通过代码分割、树摇和无用代码删除构建优化&#xff1b;基于…...

vue3 vite title 页面标题设置

效果图&#xff1a; 1. 安装 vite-plugin-html 插件 npm install vite-plugin-html -D2. 修改 vite.config.js import {defineConfig, loadEnv} from vite import { createHtmlPlugin } from "vite-plugin-html" import {resolve} from path import vue from vitej…...

spring boot添加License(软件许可)

文章目录 前言1. 生成钥匙库2. 生成证书3. 生成公匙库4.业务代码1. 引入依赖2. 关键代码3. 配置文件 5、改成线上地址&#xff0c;这样不用每次打包&#xff0c;发送license.lic文件给客户&#xff0c;重启项目就行5.1、工具类5.2 修改部分&#xff1a; 总结 前言 工作需要给软…...

LangChain打造一个AI客服

最近在学习LangChain&#xff0c;langchain的第一个入门应用就是和ChatGPT结合形成的一个AI客服&#xff0c;本期文章就带大家一起认识下 LangChain LangChain是现在用得最多的AI框架&#xff0c;langchain在帮助如基于文档数据的回答、聊天机器人和代理这类的应用程序 langch…...

【前端三剑客之JS】详解JS

1. JS的引入方式 (1). 内部脚本方式引入 在页面上&#xff0c;通过一对script标签引入js代码.script代码放置位置有一定随意性&#xff0c;一般放在head标签中. (2).外部脚本方式引入. 内部脚本只能在当前页面中使用&#xff0c;代码复用度不高.可以将脚本放在单独的js文件…...

重庆耶非凡科技有限公司有选品师项目培训吗?

在当今科技飞速发展的时代&#xff0c;各种科技公司如雨后春笋般涌现&#xff0c;它们在不同领域发挥着重要作用。其中&#xff0c;重庆耶非凡科技有限公司以其独特的业务模式和专业服务&#xff0c;在业界赢得了良好的口碑。那么&#xff0c;重庆耶非凡科技有限公司究竟是做什…...

格式转化——Labelme标注好的json文件批量转为png(标签)文件(物体为红色,背景为黑色)和jpg原图

作用如题目&#xff0c;批量将标注好的json文件转成png标签&#xff0c;jpg原图&#xff0c;其中标签时红黑图。 代码如下&#xff1a; import argparse import base64 import json import os import os.path as osp import imgviz import PIL.Image import yaml from labelm…...

力扣刷题--2535. 数组元素和与数字和的绝对差【简单】

题目描述 给你一个正整数数组 nums 。 元素和 是 nums 中的所有元素相加求和。 数字和 是 nums 中每一个元素的每一数位&#xff08;重复数位需多次求和&#xff09;相加求和。 返回 元素和 与 数字和 的绝对差。 注意&#xff1a;两个整数 x 和 y 的绝对差定义为 |x - y| 。…...

2024年【危险化学品经营单位安全管理人员】考试报名及危险化学品经营单位安全管理人员找解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 危险化学品经营单位安全管理人员考试报名考前必练&#xff01;安全生产模拟考试一点通每个月更新危险化学品经营单位安全管理人员找解析题目及答案&#xff01;多做几遍&#xff0c;其实通过危险化学品经营单位安全管…...

IntelliJ IDEA集成Baidu Comate,商城系统支付交易功能开发实战

文章目录 Baidu Comate介绍安装配置体验安装插件配置体验注释生成代码技术问答 实战设计表生成代码导入数据 总结 Baidu Comate介绍 在科技互联网飞速发展的今天&#xff0c;百度凭借其深厚的技术积累和创新能力&#xff0c;推出了一款名为Baidu Comate智能代码助手的产品。该…...

20212313 2023-2024-2 《移动平台开发与实践》第5次作业

20212313 2023-2024-2 《移动平台开发与实践》第5次作业 1.实验内容 设计并开发一个地图应用系统。 该实验需提前申请百度API Key&#xff0c;调用接口实现百度地图的定位功能、地图添加覆盖物和显示文本信息。 2.实验过程 2.1 获取SHA1 &#xff08;1&#xff09;打开控制台…...

Python图形界面(GUI)Tkinter笔记(十二):用【Entry()】实现单行文本输入(3)

Tkinter库中的单行文本输入框(Entry)除了与get()方法组合产生多姿多彩的反应,还可以与insert()方法组合而产生新的功能。例如用于用户不作任何输入就用默认值当作用户的输入这种场境,或在输入文本中加入指定的字符等。 其余笔记:【Python图形界面(GUI)Tkinter笔记(总目录…...

前端渲染页面的原理

之前一直不愿意写一篇关于原理的&#xff0c;因为说起来实在是太繁杂&#xff0c;要写得细&#xff0c;码字梳理&#xff0c;计算下来起码都要差不多三周。以前一直躲避这个事情&#xff0c;现在反正有时间&#xff0c;为了不荒废自己&#xff0c;那就从头捋一遍。也方便自己后…...

【一竞技DOTA2】RAMZES666替补参加裂变联赛

1、根据主办方文件,RAMZES666将继续作为Tundra战队替补参加裂变联赛。该比赛为欧洲线上赛,于5月27日-30日举行,总奖金8万美元。 除此之外,Nigma战队在上个月宣布四号位Matthew离队后,也选择启用老队员GH参赛。而在本月初让ah fu转回教练、携替补Thiolicor出战PGL瓦拉几亚的Secr…...

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

IGP(Interior Gateway Protocol,内部网关协议)

IGP&#xff08;Interior Gateway Protocol&#xff0c;内部网关协议&#xff09; 是一种用于在一个自治系统&#xff08;AS&#xff09;内部传递路由信息的路由协议&#xff0c;主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)

Aspose.PDF 限制绕过方案&#xff1a;Java 字节码技术实战分享&#xff08;仅供学习&#xff09; 一、Aspose.PDF 简介二、说明&#xff08;⚠️仅供学习与研究使用&#xff09;三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)

RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发&#xff0c;后来由Pivotal Software Inc.&#xff08;现为VMware子公司&#xff09;接管。RabbitMQ 是一个开源的消息代理和队列服务器&#xff0c;用 Erlang 语言编写。广泛应用于各种分布…...

为什么要创建 Vue 实例

核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...

【LeetCode】算法详解#6 ---除自身以外数组的乘积

1.题目介绍 给定一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O…...

6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础

第三周 Day 3 &#x1f3af; 今日目标 理解类&#xff08;class&#xff09;和对象&#xff08;object&#xff09;的关系学会定义类的属性、方法和构造函数&#xff08;init&#xff09;掌握对象的创建与使用初识封装、继承和多态的基本概念&#xff08;预告&#xff09; &a…...

文件上传漏洞防御全攻略

要全面防范文件上传漏洞&#xff0c;需构建多层防御体系&#xff0c;结合技术验证、存储隔离与权限控制&#xff1a; &#x1f512; 一、基础防护层 前端校验&#xff08;仅辅助&#xff09; 通过JavaScript限制文件后缀名&#xff08;白名单&#xff09;和大小&#xff0c;提…...