线程概念与控制

目录

Linux线程概念

什么是线程

分页式存储管理

虚拟地址和页表的由来

物理内存管理

页表

缺页异常

线程的优点

线程的缺点

线程异常

Linux进程VS线程

进程与线程

进程的多个线程共享

进程与线程关系如图

Linux线程控制

POSIX线程库

创建线程

测试

获取线程ID

线程终止

线程等待

测试

分离线程

测试

线程ID及进程地址空间布局

线程封装

测试


Linux线程概念
 

什么是线程
 

1.在⼀个程序⾥的⼀个执行路线就叫做线程(thread)。更准确的定义是:线程是“⼀个进程内部
的控制序列”
2.⼀切进程至少都有⼀个执行线程

3.线程在进程内部运行,本质是在进程地址空间内运行

4.在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化

5.透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程控制流

分页式存储管理

虚拟地址和页表的由来


如果在没有虚拟内存和分页机制的情况下,每一个用户程序在物理内存上所对应的空间必须是连续的

但是不同的程序他的代码与数据长度都是不一样的,并且一直会有进程退出,如果采用连续内存的方式就会导致存在很多的内存碎片。

因此,我们希望操作系统提供给用户的空间必须是连续的,但是物理内存最好不要连续。

由此,虚拟地址与页表就诞生了

把物理内存按照⼀个固定的长度的页框进行分割,有时叫做物理页。每个页框包含⼀个物理页

⼀个页的大小等于页框的大小。大多数 32位 体系结构支持4KB的页,而64位体系结
构⼀般会支持 8KB 的页。

页:一个数据块,存放于页框或磁盘上

页框:一个存储区域

有了这种机制,CPU便并非是直接访问物理内存地址,而是通过虚拟地址空间来间接的访问物理内存地址。所谓的虚拟地址空间,是操作系统为每⼀个正在执行的进程分配的⼀个逻辑地址。
 

操作系统通过将虚拟地址空间和物理内存地址之间建立映射关系,也就是页表,这张表上记录了每⼀对页和页框的映射关系,能让CPU间接的访问物理内存地址。

总结⼀下,其思想是将虚拟内存下的逻辑地址空间分为若⼲页,将物理内存空间分为若⼲页框,通过页表便能把连续的虚拟内存,映射到若干个不连续的物理内存页。这样就解决了使⽤连续的物理内存造成的碎片问题。

物理内存管理
 

假设⼀个可⽤的物理内存有4GB的空间。按照⼀个页框的大小4KB进行划分, 4GB的空间就是4GB/4KB = 1048576个页框。有这么多的物理页,操作系统肯定是要将其管理起来的,操作系统需要知道哪些页正在被使用,哪些页空闲等等。

内核用 struct page 结构表示系统中的每个物理页,出于节省内存的考虑, struct page 中使
用了大量的联合体union

注意的是 struct page 与物理页相关,而并非与虚拟页相关。⽽系统中的每个物理页都要分配⼀
个这样的结构体,让我们来算算对所有这些页都这么做,到底要消耗掉多少内存。

我们算一个struct page 40个字节,一个页4kb,那么一个struct page的总数大概为页总数的1/100。

系统4GB的空间,struct page大概占40M,相对于系统的4GB内存来说并不算多

要知道的是,页的大小对于内存利用和系统开销来说非常重要,页太大,页必然会剩余较大不能利用的空间(页内碎片)。页太小,虽然可以减小页内碎片的大小,但是页太多,会使得页表太长而占用内存,同时系统频繁地进行页转化,加重系统开销。因此,页的大小应该适中,通常512B -8KB ,windows系统的页框大小为4KB。

页表
 

页表中的每⼀个表项,指向⼀个物理页的开始地址。在 32 位系统中,虚拟内存的最大空间是 4GB ,这是每⼀个用户程序都拥有的虚拟内存空间。既然需要让 4GB 的虚拟内存全部可用,那么页表中就需要能够表示这所有的 4GB 空间,那么就一共需要 4GB/4KB = 1048576 个表项。

虚拟内存仍然是连续的,图中的虚线只是用来表示虚拟内存单元与页表每一个表项的映射关系

最终实现,虚拟地址上连续,物理地址上分散,并且解决了内存碎片化的问题

提问

在 32 位系统中,地址的长度是 4 个字节,那么页表中的每⼀个表项就是占用 4 个字节。所以页表占据的总空间大小就是: 1048576*4 = 4MB的大小。也就是说映射表自己本身,就要占用4MB /4KB = 1024 个物理页。这会存在哪些问题呢?

1.我们创建页表的目的就是为了将进程划分为可以一个个页,可以不用连续的存放在物理地址。

但是我们页表自己就需要1024个连续物理页,与我们一开始的想法冲突

2.很多时候进程都是需要访问部分物理页,没有必要让所有物理页都一直占据内存空间

解答

解决大容量页表的方法就是将页表也看作文件,对页表也进行分页,因此,多级页表的思想就产生了。

将一个大页表拆成1024个小页表(每个表1024个表项),这样,1024(表的个数)*1024(每个表中表项)个4k的小页表同样可以占据4GB的物理内存空间。

从总数上看,整张大页表仍然需要4M空间,似乎和之前没区别,但实际上一个应用程序不可能占据4GB的所有内存空间,或许几十个小页表就够了,一个程序的代码段,数据段,栈段一共需要10M,也就是3张小页表就够了

缺页异常
 

CPU给MMU的虚拟地址,在 TLB 和页表都没有找到对应的物理页,该怎么办呢?其实这就是缺页异常 Page Fault ,它是⼀个由硬件中断触发的可以由软件逻辑纠正的错误。

由于CPU没有数据就无法进行计算,CPU罢工了,用户进程也就出现了缺页中断,进程会从用户态切换到内核态,并将缺页中断交给内核的Page Fault Handler处理。

缺页中断会交给 PageFaultHandler 处理,其根据缺页中断的不同类型会进行不同的处理:
 

1.Hard Page Fault 也被称为 Major Page Fault ,翻译为硬缺页错误/主要缺页错误,这
时物理内存中没有对应的物理页,需要CPU打开磁盘设备读取到物理内存中,再让MMU建立虚拟
地址和物理地址的映射。

2.Soft Page Fault 也被称为 Minor Page Fault ,翻译为软缺页错误/次要缺页错误,这
时物理内存中是存在对应物理页的,只不过可能是其他进程调入的,发出缺页异常的进程不知道
而已,此时MMU只需要建立映射即可,无需从磁盘读取写⼊内存,⼀般出现在多进程共享内存区
域。
3.Invalid Page Fault 翻译为无效缺页错误,比如进程访问的内存地址越界访问,右比如对
空指针解引用内核就会报 segment fault 错误中断进程直接挂掉。

线程的优点
 

1.创建一个新线程代价比创建一个新进程代价小得多

2.与进程切换相比,线程切换操作系统要做的工作也小很多

例如:1.由于线程属于同一个进程,拥有相同的虚拟地址空间。2.进程上下文的切换会扰乱处理器的缓存机制,这将导致内存的访问在一段时间内效率很低,在线程的切换中就没有这个问题。

3.线程占用的资源要比进程小很多

4.能充分利用多处理器的可并行数量

5.在等待慢速IO工作完成的同时也可以执行计算任务

6.计算密集型应用为了在多处理器系统上运行,将计算分解到多线程中运行

7.IO密集型应用,为了提高性能将I/O操作重叠。线程可同时等待不同的I/O操作

线程的缺点
 

性能损失
⼀个很少被外部事件阻塞的计算密集型线程往往无法与其它线程共享同⼀个处理器。如果计
算密集型线程的数量⽐可⽤的处理器多,那么可能会有较大的性能损失,这⾥的性能损失指
的是增加了额外的同步和调度开销,而可用的资源不变。
健壮性降低
编写多线程需要更全⾯更深⼊的考虑,在⼀个多线程程序里,因时间分配上的细微偏差或者
因共享了不该共享的变量⽽造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护
的。
缺乏访问控制
进程是访问控制的基本粒度,在⼀个线程中调用某些OS函数会对整个进程造成影响。
编程难度提高

线程异常
 

一个进程中的多个线程同属于该进程,那就意味着一旦某个进程出现异常例如野指针导致的不仅仅是那个线程退出,还是整个进程的退出。因为线程出了异常就是进程出异常。

Linux进程VS线程
 

进程与线程

1.进程是资源分配的基本单位(所有线程共享进程资源)

2.线程是调度的基本单位(这意味着每个线程分配到的时间片和进程本身无关)

3.线程共享一部分数据,同时还拥有自己的一部分数据:

线程ID、一组寄存器、栈、errno信号屏蔽字、调度优先级

进程的多个线程共享
 

同⼀地址空间,因此Text Segment、Data Segment都是共享的,如果定义⼀个函数,在各线程中都可以调用,如果定义⼀个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
文件描述符表、每种信号的处理方式、当前工作目录、用户ID与组ID

进程与线程关系如图

Linux线程控制
 

POSIX线程库

1.与线程有关的函数构成了⼀个完整的系列,绝大多数函数的名字都是以“pthread_”打头的

2.要使用这些函数库,要引入头文件<pthread.h>

3.连接这些函数库需要使用编译器命令的 -lpthread选项,也就是使用系统路径下的libpthread.so 动态库,头文件与库文件都在系统默认路径下

创建线程

#include<pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);thread:返回线程ID
attr:设置线程属性,设置为nullptr则采用默认配置
start_routine:是个函数地址,线程启动后要执⾏的函数
arg:传给线程启动函数的参数返回值:成功返回0
错误返回错误码

测试

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
void* run(void* arg)
{int i=0;while(true){i++;printf("我是线程1:%d\n",i);sleep(1);}
}int main()
{pthread_t tid;int ret;if(ret=pthread_create(&tid,nullptr,run,nullptr)!=0){fprintf(stderr,"pthread create:%s",strerror(ret));exit(EXIT_FAILURE);}while(true){printf("我是主线程\n");sleep(1);}
}

获取线程ID

#include<pthread.h>pthread_t pthread_self(void);

这个函数返回“进程级线程ID

这个“ID”是pthread库给每个线程定义的进程内唯⼀标识,是pthread库维持的

当然了,这个ID是进程级的,操作系统并不认识

其实pthread库也是通过内核提供的系统调⽤(例如clone)来创建线程的,而内核会为每个线程创建系统全局唯⼀的“ID”来唯⼀标识这个线程。

-L 选项:打印线程信息
 

我们可以看到,两个线程拥有相同的进程ID,但是操作系统为他们分配了不同的“LWP(即系统级线程ID)

LWP得到的是真正的线程ID。之前使⽤pthread_self得到的这个数实际上是⼀个地址,在虚拟地址空间上的⼀个地址,通过这个地址,可以找到关于这个线程的基本信息,包括线程ID,线程栈,寄存器等属性。

ps -aL看到的线程ID,有⼀个线程ID和进程ID相同,这个线程就是主线程,主线程的栈在虚拟
地址空间的栈上,而其他线程的栈在是在共享区(堆栈之间),因为pthread系列函数都是pthread库提供给我们的。而pthread库是在共享区的。所以除了主线程之外的其他线程的栈都在共享区。


线程终止

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

1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调⽤exit。
2. 线程可以调pthread_exit终止自己
3. ⼀个线程可以调⽤pthread_cancel终⽌同⼀进程中的另⼀个线程。

pthread_exit函数
功能:线程终止void pthread_exit(void *value_ptr);value_ptr:value_ptr不要指向⼀个局部变量,因为这个返回值是要交给其他线程查看的,
如果指向局部变量那么线程结束时,局部变量也会被一起销毁
因此,value_ptr最好是全局变量的地址或者是堆上开辟的空间⽆返回值,跟进程⼀样,线程结束的时候⽆法返回到它的调⽤者(⾃⾝)

如果采用return返回,那么要求也和pthread_exit相同,不要指向一个局部变量
 

功能:取消⼀个执⾏中的线程int pthread_cancel(pthread_t thread);thread:线程ID返回值:
成功返回0
失败返回错误码

线程等待
 

为什么需要线程等待

已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
创建新的线程不会复用刚才退出线程的地址空间。
 

这里其实和进程等待差不多,如果不等待的话那么线程也会一直占有空间不释放,相当于“僵尸线程

功能:等待线程结束int pthread_join(pthread_t thread, void **value_ptr);thread:指定线程IDvalue_ptr:因为线程的返回值是一个一级指针,
所以我们就需要使用二级指针才能拿到线程退出时返回的值返回值:
成功返回0
失败返回错误码

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过
pthread_join得到的终止状态是不同的,总结如下:

1.使用return 或pthread_exit函数退出,我们知道exit和return非常类似,所以使用这两种方式退出时,value_ptr指针上的值就是return的返回值或时传给pthread_exit函数的参数。

2.如果线程是被别的线程使用pthread_cancel终止掉的,value_ptr所指向的单元里存放的是
就为常数PTHREAD_CANCELED

3.如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数

测试

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *thread1(void *arg)//1号线程return返回,在堆上开辟空间带出返回值
{printf("thread 1 returning ... \n");int *p = (int *)malloc(sizeof(int));*p = 1;return (void *)p;
}
void *thread2(void *arg)//2号线程pthread_exit返回,一样在堆上开辟空间
{printf("thread 2 exiting ...\n");int *p = (int *)malloc(sizeof(int));*p = 2;pthread_exit((void *)p);
}
void *thread3(void *arg)//3号线程被主线程pthread_cancel返回,返回值默认为常数PTHREAD_CANCELED
{while (1){ //printf("thread 3 is running ...\n");sleep(1);}return NULL;
}int  main(void)
{pthread_t tid;void *ret;// thread 1 returnpthread_create(&tid, NULL, thread1, NULL);pthread_join(tid, &ret);printf("thread return, thread id %lX, return code:%d\n", tid, *(int *)ret);free(ret);// thread 2 exitpthread_create(&tid, NULL, thread2, NULL);pthread_join(tid, &ret);printf("thread return, thread id %lX, return code:%d\n", tid, *(int *)ret);free(ret);// thread 3 cancel by otherpthread_create(&tid, NULL, thread3, NULL);sleep(3);pthread_cancel(tid);pthread_join(tid, &ret);if (ret == PTHREAD_CANCELED)printf("thread return, thread id %lX, return code:PTHREAD_CANCELED\n",tid);elseprintf("thread return, thread id %lX, return code:NULL\n", tid);
}

分离线程
 

默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则
无法释放资源,从而造成系统泄漏。
如果不关心线程的返回值,join是⼀种负担,这个时候,我们可以告诉系统,当线程退出时,⾃
动释放线程资源。

功能:分离线程,线程退出时自动释放资源int pthread_detach(pthread_t thread);thread:指定线程ID返回值:
成功返回0
失败返回错误码

我们可以选择让线程自己分离自己,也可以选择让其他线程来进行分离

thread_detach(pthread_self());thread_datach(thread_id);


joinable和分离是冲突的,⼀个线程不能既是joinable又是分离的。


测试

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *thread_run(void *arg)
{pthread_detach(pthread_self());printf("%s\n", (char *)arg);return NULL;
}
int main(void)
{pthread_t tid;if (pthread_create(&tid, NULL, thread_run, (void*)"thread1 run...") != 0){printf("create thread error\n");return 1;}int ret = 0;sleep(1); // 很重要,要让线程先分离,再等待if (pthread_join(tid, NULL) == 0){printf("pthread wait success\n");ret = 0;}else{printf("pthread wait failed\n");ret = 1;}return ret;
}

线程ID及进程地址空间布局
 

pthread_create函数会产生⼀个进程级线程ID,存放在第⼀个参数指向的地址中。
前面讲的系统级线程ID属于进程调度的范畴,因为线程是轻量级进程,是操作系统调度的最小单位,所以需要唯一的一个数值来表示该线程

pthread_create函数第⼀个参数指向⼀个虚拟内存单元,该内存单元的地址即为新创建线程的线
程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
 

线程库NPTL提供了pthread_self函数,可以获得线程自身的ID:
 

pthread_t pthread_self(void);

pthread_t 类型取决于实现,对于linux目前的NPTL而言,pthread_t类型的线程ID实际上,本质上是进程地址空间上的一个地址。

线程封装


 

#pragma once
#include <iostream>
#include <pthread.h>
#include <functional>
#include <string>
#include <cstdint>namespace ThreadModule
{std::uint32_t cnt; // 原子计数器,形成线程名称class Thread{public:using work_t = std::function<void()>;enum class TSTATUS{THREAD_NEW,THREAD_RUNNING,THREAD_STOP};Thread(work_t work) : _status(TSTATUS::THREAD_NEW),_joined(true),_func(work){SetName();}void EnableJoined(){if (_status == TSTATUS::THREAD_NEW)_joined = true;}void EnableDetach(){if (_status == TSTATUS::THREAD_NEW)_joined = false;}bool Start(){if (_status == TSTATUS::THREAD_RUNNING)return true;int n = pthread_create(&_id, nullptr, Run, (void *)this);if (n != 0)return false;return true;}bool Join(){if (_joined){int n = pthread_join(_id, nullptr);if (n != 0)return false;return true;}return _status==TSTATUS::THREAD_STOP;}~Thread() {}private:static void *Run(void *obj){Thread *self = static_cast<Thread *>(obj);self->_status = TSTATUS::THREAD_RUNNING;pthread_setname_np(self->_id, self->_name.c_str()); // 为当前线程取名字if (self->_joined == false)pthread_detach(pthread_self());self->_func();self->_status=TSTATUS::THREAD_STOP;return nullptr;}void SetName(){_name = "Thread-" + std::to_string(cnt++);}private:std::string _name;pthread_t _id;TSTATUS _status;bool _joined;work_t _func;};
}

测试

#include"thread.hpp"
#include<vector>
void test_printf(int num)
{std::cout<<num;
}
int main()
{std::vector<ThreadModule::Thread> vec;for(int i=0;i<10;++i){auto func=std::bind(test_printf,i);vec.emplace_back(func);}for(auto& e:vec)e.Start();for(auto& e:vec)e.Join();std::cout<<std::endl;return 0;
}

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

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

相关文章

Linux --进度条小程序更新

这里使用随机数来模拟下载量&#xff0c;来实现一个下载进度更新的小程序 main.c 的代码&#xff0c;其中downlod这个函数使用的是函数指针&#xff0c;如果有多个进度条函数可以传入进行多样化的格式下载显示&#xff0c;还需要传入一个下载总量&#xff0c;每次"下载以…

【算法】贪心算法

一、贪心算法基本思想 贪心算法总是作出在当前看来最好的选择。也就是说贪心算法并不从 整体最优考虑&#xff0c;它所作出的选择只是在某种意义上的局部最优选择。 我们希望贪心算法得到的最终结果也是整体最优的。虽然贪心算法不 能对所有问题都得到整体最优解&#xff08;O…

MySQL事务与锁机制详解:确保数据一致性的关键【MySQL系列】

本文将系统讲解 MySQL 中事务的四大特性、隔离级别与实现原理&#xff0c;深入拆解锁机制的种类与应用场景&#xff0c;并结合典型死锁案例进行分析&#xff0c;为你构建起应对复杂一致性问题的坚实基础。 一、什么是事务&#xff1f; 事务&#xff08;Transaction&#xff09…

UE5 Mat HLSL - Load

特性Load()Sample()输入类型整数索引&#xff08;int2/int3&#xff09;浮点 UV 采样器状态&#xff08;SamplerState&#xff09;数据获取精确读取指定位置的原始数据基于 UV 插值和过滤后的数据典型用途精确计算、非过滤访问&#xff08;如物理模拟&#xff09;纹理贴图渲染…

基于vue框架的独居老人上门护理小程序的设计r322q(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,护理人员,服务预约,服务评价,服务类别,护理项目,请假记录 开题报告内容 基于Vue框架的独居老人上门护理小程序的设计开题报告 一、研究背景与意义 &#xff08;一&#xff09;研究背景 随着社会老龄化的加剧&#xff0c;独居老…

鸿蒙如何引入crypto-js

import CryptoJS from ohos/crypto-js 报错。 需要先安装ohom&#xff1a;打开DevEco&#xff0c;点击底部标签组&#xff08;有Run, Build, Log等&#xff09;中的Terminal&#xff0c;在Terminal下执行&#xff1a; ohpm install 提示 install completed in 0s 119ms&…

【C++】入门基础知识(1.5w字详解)

本篇博客给大家带来的是一些C基础知识&#xff01;包含函数栈帧的详解&#xff01; &#x1f41f;&#x1f41f;文章专栏&#xff1a;C &#x1f680;&#x1f680;若有问题评论区下讨论&#xff0c;我会及时回答 ❤❤欢迎大家点赞、收藏、分享&#xff01; 今日思想&#xff1…

二.MySQL库的操作

一.创建数据库create database 名称; 字符集和校验规则 一、字符集&#xff08;Character Set&#xff09; 表示数据库中可以使用哪些字符。 例如&#xff1a;utf8 可以存储包括中文在内的多种语言字符&#xff0c;gbk 更适合中文字符环境。 功能举例控制支持哪些语言字符utf…

【Linux 学习计划】-- 命令行参数 | 环境变量

目录 命令行参数 环境变量 环境变量的本质是什么&#xff1f; 相关配置文件 修改环境变量的相关操作 代码获取env —— environ 内建命令 结语 命令行参数 试想一下&#xff0c;我们的main函数&#xff0c;也是一个函数&#xff0c;那么我们的main函数有没有参数呢&am…

具有离散序列建模的统一多模态大语言模型【AnyGPT】

第1章 Instruction 在人工智能领域、多模态只语言模型的发展正迎来新的篇章。传统的大型语言模型(LLM)在理解和生成人类语言方面展现出了卓越的能力&#xff0c;但这些能力通常局限于 文本处理。然而&#xff0c;现实世界是一个本质上多模态的环境&#xff0c;生物体通过视觉、…

git查看commit属于那个tag

1. 快速确认commit原始分支及合入tag # git describe 213b4b3bbef2771f7a1b8166f6e6989442ca67c8 查看commit合入tag # git describe 213b4b3bbef2771f7a1b8166f6e6989442ca67c8 --all 查看commit原始分支 2.查看分支与master关系 # git show --all 0.5.67_0006 --stat 以缩…

day10机器学习的全流程

浙大疏锦行 1.读取数据 import pandas as pd import pandas as pd #用于数据处理和分析&#xff0c;可处理表格数据。 import numpy as np #用于数值计算&#xff0c;提供了高效的数组操作。 import matplotlib.pyplot as plt #用于绘制各种类型的图表# 设置中文字体…

基于对比学习的推荐系统开发方案,使用Python在PyCharm中实现

以下是一个基于对比学习的推荐系统开发方案,使用Python在PyCharm中实现。本文将详细阐述技术原理、系统设计和完整代码实现。 基于对比学习的推荐系统开发方案 一、技术背景与原理 1.1 对比学习核心思想 对比学习(Contrastive Learning)通过最大化正样本相似度、最小化负…

2025山东CCPC题解

文章目录 L - StellaD - Distributed SystemI - Square PuzzleE - Greatest Common DivisorG - Assembly Line L - Stella 题目来源&#xff1a;L - Stella 解题思路 签到题&#xff0c;因为给出的字母不是按顺序&#xff0c;可以存起来赋其值&#xff0c;然后在比较。 代码…

某航参数逆向及设备指纹分析

文章目录 1. 写在前面2. 接口分析3. 加密分析4. 算法还原5. 设备指纹风控分析与绕过 【&#x1f3e0;作者主页】&#xff1a;吴秋霖 【&#x1f4bc;作者介绍】&#xff1a;擅长爬虫与JS加密逆向分析&#xff01;Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享…

Python训练营---Day41

DAY 41 简单CNN 知识回顾 数据增强卷积神经网络定义的写法batch归一化&#xff1a;调整一个批次的分布&#xff0c;常用与图像数据特征图&#xff1a;只有卷积操作输出的才叫特征图调度器&#xff1a;直接修改基础学习率 卷积操作常见流程如下&#xff1a; 1. 输入 → 卷积层 …

【Netty系列】Reactor 模式 2

目录 流程图说明 关键流程 以下是 Reactor 模式流程图&#xff0c;结合 Netty 的主从多线程模型&#xff0c;帮助你直观理解事件驱动和线程分工&#xff1a; 流程图说明 Clients&#xff08;客户端&#xff09; 多个客户端&#xff08;Client 1~N&#xff09;向服务端发起连…

前端开发中 <> 符号解析问题全解:React、Vue 与 UniApp 场景分析与解决方案

前端开发中 <> 符号解析问题全解&#xff1a;React、Vue 与 UniApp 场景分析与解决方案 在前端开发中&#xff0c;<> 符号在 JSX/TSX 环境中常被错误解析为标签而非比较运算符或泛型&#xff0c;导致语法错误和逻辑异常。本文全面解析该问题在不同框架中的表现及解…

【Web应用】 Java + Vue 前后端开发中的Cookie、Token 和 Swagger介绍

文章目录 前言一、Cookie二、Token三、Swagger总结 前言 在现代的 web 开发中&#xff0c;前后端分离的架构越来越受到欢迎&#xff0c;Java 和 Vue 是这一架构中常用的技术栈。在这个过程中&#xff0c;Cookie、Token 和 Swagger 是三个非常重要的概念。本文将对这三个词进行…

投稿Cover Letter怎么写

Cover Letter控制在一页比较好&#xff0c;简短有力地推荐你的文章。 Dear Editors: Small objects detection in remote sensing field remains several challenges, including complex backgrounds, limited pixel representation, and dense object distribution, which c…