Linux从0到1——初识线程【什么是线程/线程控制/详解tid】

Linux从0到1——初识线程

  • 1. 什么是线程?
    • 1.1 线程的概念
    • 1.2 结合代码理解线程
    • 1.3 重谈地址空间
    • 1.4 线程的优缺点
  • 2. 线程异常和线程用途
  • 3. Linux进程VS线程
  • 4. 详解pthread_create的arg参数
    • 4.1 给线程传参
    • 4.2 一次创建多个线程
  • 5. 线程控制
    • 5.1 线程退出
    • 5.2 线程分离
    • 5.3 线程取消
  • 6. 详解线程id


1. 什么是线程?


1.1 线程的概念


1. 线程概念

  • 课本上的概念:
    • 线程是比进程更加轻量化的一种执行流;
    • 线程是在进程内部执行的一种执行流。
  • 我们的理解:
    • 线程是CPU调度的基本单位;
    • 进程是承担系统资源的基本实体。

2. 理解线程

在这里插入图片描述

  • 每创建一个进程,就需要创建对应的地址空间mm_struct,PCB,页表。开销实际上是比较大的。那么可不可以设计一种更轻量级的进程?让这种轻量级进程,和主进程共用一个地址空间,共用一个页表?
  • 上面的方案从技术上来说一定是可行的,我们只需要另外为这个轻量级进程创建一个PCB,然后将主进程的地址空间划分出一块给这个新进程即可。我们将这种轻量级进程,叫做“线程”。
  • “线程”只参与资源的分配,而不参与资源的创建。

3. 线程的管理(Linux)

  • OS中如果要支持线程,也就必须要对它进行管理,即先描述,再组织。那是不是意味着,需要再设计一个TCB即线程控制块,来描述它?以及再设计一套线程的调度方法?这样是不是有点太复杂了。
  • 实际上,PCB中已经包含了一个执行流需要的所有信息,而且基于PCB的调度算法也是十分完备的。所以线程只需要复用PCB,和PCB的调度方法即可。

4. Linux中线程的实现方案

  • 很多操作系统的书籍中,只是告诉你一个操作系统中应该有什么,而不会告诉你具体怎么实现。所以,操作系统更像是一种指导文档,告诉我们一个线程应该有什么样的特点,而不会告诉我们具体怎么实现。
  • 我们上面谈的线程管理,只是Linux中对线程的管理方案。线程复用PCB。
  • 从CPU的角度来看,如何区分这个PCB是线程还是进程?答案是不需要区分,CPU的任务只是调度执行流,不关心是进程还是线程。就好比一个快递员的主要任务是发送快递,而不关心快递包装中具体装的是什么。
  • Linux中不存在严格意义上的线程,CPU在调度时,将每一个PCB都看作是一个“轻量级进程(Light Weight Process)”,线程只是对用户层的一个封装。CPU中的基本调度单位是轻量级进程LWP

5. 如何看待之前学习的进程,和今天所学的进程?

  • 之前所学的进程,是内部只有一个执行流的进程;
  • 今天学习的是内核中有多个执行流的进程。

6. 从CPU调度的角度,理解线程的轻量化

  • 之前我们只是提到了,线程的创建更加轻量化,只需要创建对应的PCB即可。
  • 就CPU调度而言,切换进程,需要切换CPU中大量的寄存器信息,包括页表的,地址空间的等等。但是对于线程,只需要切换一些临时寄存器中的信息即可。
  • 上面提到的仍然不是线程切换解决的主要矛盾,线程切换真正的优势和CPU中的一个硬件cache有关:
    • 根据局部性原理,访问某一行代码时,下一次有较大概率访问这段代码前后一部分的代码。比如现在程序运行到了main函数的第10行,那么极有可能下一次运行的就是0到20行中的其中一段代码。科学家们根据这一原理设计出了cache,它的作用是将一段代码的前后多行代码加载进CPU,下一次访问时先在cache中查找有没有要执行的代码,如果没有再去内存中找,提高了效率。我们将cache中的数据叫作热数据。
    • 进程在切换时,需要重新对cache中的数据做热加载,即刷新cache。而线程切换则不需要刷新cache。这就是线程最主要的优势。

7. 进程时间片也要被内部的线程瓜分,时间片也是资源

  • 如果OS会为每一个线程也分配时间片,这就是一个严重的bug。用户可以通过不断创建线程,来抢占CPU资源,获得优先调度权。

1.2 结合代码理解线程


1. pthread_create函数

在这里插入图片描述

  • 可以看到该接口在3号手册中,所以这不是一个系统接口,而是库函数。
  • 参数:
    • pthread_t *thread:输出型参数,返回线程标识符;
    • const pthread_attr_t *attr:指向线程属性对象,定义了线程的属性,如果不需要特殊属性可以传入 NULL
    • void *(*start_routine) (void *):线程启动时执行的函数,它必须返回一个 void* 类型的值,并且接受一个 void* 类型的参数。
    • void *arg:传递给 start_routine 函数的参数。

这里了解一下即可,之后还会细讲。

2. 实验代码

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>void *ThreadRoutine(void *args)
{usleep(1000);   // 让主线程先打印const char *threadname = (const char *)args;while(true){std::cout << "I am a new thread:" << threadname << ", pid:" << getpid() << std::endl;sleep(1); }
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRoutine, (void*)"thread 1");   // 创建线程// 主线程while(true){std::cout << "I am main thread" << ", pid: " << getpid() << std::endl;sleep(1);}return 0;
}
  • 由于<pthread.h>线程库是第三方库,所以编译时要带上选项-lpthread

3. 实验现象

  • ps -aL命令会列出所有终端的进程,并且对于每个多线程的进程,它会显示该进程下所有线程的详细信息。

在这里插入图片描述

  • 上图中显示的LWP这一列对应的数据是轻量级进程id,可以看到,两个线程的PID相同,属于同一进程,并且主线程的PIDLWP相同。

1.3 重谈地址空间


1. 页帧和页框

在这里插入图片描述

  • 文件系统IO的基本单位大小为4KB,这个大小又叫Page size。因为有这个规则的存在,所以从逻辑上将文件hello.exe划分成一块一块的4kb空间,每一块叫做一个页帧。
  • 将磁盘文件换入到内存时,也是按4kb的大小换入的,为了适配页帧,物理内存也划分出了一个一个4kb大小的页框。
  • 有了页框,OS还需要知道每个页框的使用情况,有没有被占用,是否异常等各种信息。先描述,再组织。
struct page
{// 描述一个页框的使用情况// page的属性int flag;...
}
  • 有了描述页框的结构体,还需要将他们管理起来,使用了数组struct page pages[1048576],所以,对内存的管理,就变成了对数组内容的增删查改。

2. 重谈页表

在这里插入图片描述

  • 假设现在是32位机器,那么虚拟地址就有232个。如果按照我们之前理解的页表,一个虚拟地址对应一个物理地址,假设页表中一行有8byte(当前不止8byte,只是假设),那么一个页表的大小就有4GBX8byte,这远远超出了内存的承受范围。所以之前的理解是片面的,是不正确不严谨的。

在这里插入图片描述

  • 真实的页表结构如上图所示。32位机器的虚拟地址有32位,前10位是一个数组下标,通过该下标可以在页目录中找到下一级的页表。中间10位也是数组下标,通过该下标可以找到物理内存中的页框起始地址。最后12位则是偏移量,通过这样一套页框起始地址+偏移量的方式,就可以精准定位到物理内存中的每一个字节了。
  • 为什么偏移量有12位?因为一个页框的大小是4KB,正好就是212字节。这样一来,页表最大也就只有220x4byte,远远小于之前的232x8byte。
  • 并且页表是动态开辟的,一个页表用完了,就再开辟一个,这就又缩小了页表的大小。

3. 划分进程的本质

  • 划分进程资源的本质,就是划分页表。划分页表的本质,是划分地址空间。

1.4 线程的优缺点


1. 线程的优点

  • 创建一个新线程的代价要比创建一个新进程小得多;
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
    线程占用的资源要比进程少很多;
  • 能充分利用多处理器的可并行数量;
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务;
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现;
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

2. 线程的缺点

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

2. 线程异常和线程用途


1. 线程异常

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

2. 线程用途

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

3. Linux进程VS线程


1. 进程VS线程

  • 进程是资源分配的基本单位;
  • 线程是调度的基本单位。

2. 线程共享进程数据,但也拥有自己的一部分数据

  • 线程ID;
  • 一组寄存器,独立的硬件上下文数据;
  • 独立的栈结构;
  • errno
  • 信号屏蔽字;
  • 调度优先级。

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

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

4. 进程和线程关系图

在这里插入图片描述


4. 详解pthread_create的arg参数


4.1 给线程传参


通过该函数的arg参数给线程传参。argvoid*类型的参数,这就说明我们可以传任意类型的参数。以下是一个传参的示例,使用了一个自定义类型描述线程的相关信息。

#include <iostream>
#include <string>
#include <functional>
#include <time.h>
#include <unistd.h>
#include <pthread.h>// typedef std::function<void()> func_t; 下面的写法和这句代码等价
using func_t = std::function<void()>;   // C++11新语法// 线程的相关数据
class ThreadData
{
public:ThreadData(const std::string &name, const uint64_t &ctime, func_t f):threadname(name), createtime(ctime), func(f){}public:std::string threadname; // 线程名uint64_t createtime;    // 线程创建时间func_t func;            // 该线程执行的方法
};void Print()
{std::cout << "我是线程执行的大任务的一部分" << std::endl;
}// 新线程
void *ThreadRountine(void *args)
{ThreadData *td = static_cast<ThreadData*>(args);while(true){std::cout << "new thread" << std::endl;std::cout << "thread name: " << td->threadname << std::endl;std::cout << "create time: " << td->createtime << std::endl;td->func();sleep(3);}
}// 主线程
int main()
{pthread_t tid;ThreadData *td = new ThreadData("thread 1", (uint64_t)(time(nullptr)), Print);// 传参直接传tdpthread_create(&tid, nullptr, ThreadRountine, td);while(true){std::cout << "main thread" << std::endl;sleep(1);}return 0;
}

在这里插入图片描述


4.2 一次创建多个线程


#include <iostream>
#include <string>
#include <functional>
#include <vector>
#include <time.h>
#include <unistd.h>
#include <pthread.h>// typedef std::function<void()> func_t; 下面的写法和这句代码等价
using func_t = std::function<void()>;#define threadnum 5class ThreadData
{
public:ThreadData(const std::string &name, const uint64_t &ctime, func_t f):threadname(name), createtime(ctime), func(f){}public:std::string threadname;uint64_t createtime;func_t func;
};// 新线程
void *ThreadRountine(void *args)
{ThreadData *td = static_cast<ThreadData*>(args);while(true){std::cout << "new thread." << " thread name: " << td->threadname << " create time: " << td->createtime << std::endl;sleep(1);}
}int main()
{std::vector<pthread_t> pthreads;    // 存储线程的tidfor (size_t i = 0; i < threadnum; i++){char threadname[64];snprintf(threadname, sizeof(threadname), "%s-%lu", "thread", i);pthread_t tid;ThreadData *td = new ThreadData(threadname, (uint64_t)(time(nullptr)), nullptr);pthread_create(&tid, nullptr, ThreadRountine, td);pthreads.push_back(tid);sleep(1);       // 隔一秒创建一个线程}while(true){sleep(1);}return 0;
}

在这里插入图片描述


5. 线程控制


5.1 线程退出


1. 线程退出的两种方式

  • 直接return
  • 使用pthread_exit()函数;
    • 该函数的retval参数,就是线程想返回给主进程的内容。

在这里插入图片描述

2. 主线程如何获得新线程的退出信息?

  • pthread_join()函数可以等待线程退出,类似于进程等待。线程退出,没有等待,会导致类似进程的僵尸问题。
  • 其中thread参数指的是要等待的线程idretval参数则是用来接收新线程的返回值。

在这里插入图片描述

  • 返回值:等待成功返回0;不成功返回错误码errno

3. 以下是一个完成的线程等待+获取退出信息示例

#include <iostream>
#include <string>
#include <functional>
#include <time.h>
#include <unistd.h>
#include <pthread.h>class ThreadReturn
{
public:ThreadReturn(pthread_t id, const std::string &info, int code):_id(id), _info(info), _code(code){}
public:pthread_t _id;      // 线程idstd::string _info;  // 退出信息int _code;          // 退出码
};void *threadRoutine(void *arg)
{std::string name = static_cast<const char*>(arg);int cnt = 5;while(cnt--){std::cout << "new thread is running, thread name: " << name << " tid: " << pthread_self() << std::endl;   // pthread_self函数返回线程自己的idsleep(1);}ThreadReturn *ret = new ThreadReturn(pthread_self(), "thread quit normal", 10);// 以下两种线程退出方式任选其一// 1. return ret;         // 2. pthread_exit(ret);// 这里选第一种return ret;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread-1");// 线程默认要被等待!//  1. 线程退出,没有等待,会导致类似进程的僵尸问题//  2. 线程退出时,主线程如何获取新线程的返回值?void *ret = nullptr;int n = pthread_join(tid, &ret);ThreadReturn *r = static_cast<ThreadReturn*>(ret);std::cout << "main thread get new thread return: " << r->_id << ", " << r->_info<< ", " << r->_code<< std::endl;std::cout << "main thread done. n: " << n << std::endl;return 0;
}

在这里插入图片描述


5.2 线程分离


1. 线程分离的使用场景

  • 在多线程场景中,如果主线程pthread_join了某一个新线程,且该新线程未退出,则主线程会发生阻塞等待。如果我们不希望主线程阻塞等待该新线程,可以将该新线程设置为分离状态。分离状态下的新线程,退出后会被自动回收,不需要主线程进行等待。
  • 当主线程不关心新线程任务的完成情况时,就可以使用线程分离。

2. 设置线程分离的两种方式

在这里插入图片描述

  • 直接在新线程的任务块中,调用pthread_detach方法:
// 新线程任务块
void *threadRoutine(void *arg)
{// 线程分离pthread_detach(pthread_self());...
}// 主线程
int main()
{...
}
  • 在主线程中,调用pthread_detach
// 新线程
void *threadRoutine(void *arg)
{...
}// 主线程
int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread-1");// 线程分离pthread_detach(tid);...
}

如果新线程被设置分离了,主线程还pthread_join它,错误码被设置,返回22。


5.3 线程取消


1. pthread_cancel函数

在这里插入图片描述

  • 功能是取消一个执行中的线程。
  • 参数thread是线程id
  • 执行成功返回0,失败返回错误码。

2. 代码示例

#include <iostream>
#include <string>
#include <functional>
#include <time.h>
#include <unistd.h>
#include <pthread.h>void *threadRoutine(void *arg)
{std::string name = static_cast<const char*>(arg);while(1){std::cout << "new thread is running, thread name: " << name << " tid: " << pthread_self() << std::endl;   // pthread_self函数返回线程自己的idsleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread-1");// 3秒后取消新线程sleep(3);pthread_cancel(tid);// 等待新线程void *ret = nullptr;int n = pthread_join(tid, &ret);std::cout << "main thread done. n: " << n << " ret: " << (int64_t)ret << std::endl;return 0;
}

在这里插入图片描述

  • 这里新线程被主线程取消了,虽然新线程没有执行自己的return退出语句,但是主线程也拿到了ret返回值,是-1。被取消的线程的返回值,默认都是-1。

分离状态的线程也可以被取消,分离和取消不冲突,但是和等待pthread_join冲突。


6. 详解线程id


1. 问题引入

#include <iostream>
#include <string>
#include <functional>
#include <time.h>
#include <unistd.h>
#include <pthread.h>void *threadRoutine(void *arg)
{std::string name = static_cast<const char*>(arg);while(1){std::cout << "new thread is running, thread name: " << name << " tid: " << pthread_self() << std::endl;   // pthread_self函数返回线程自己的idsleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread-1");while(true){std::cout << "main thread, sub thread: " << tid << std::endl;sleep(1);}return 0;
}

在这里插入图片描述

  • 为什么LWP,即轻量级进程id,和线程的tid不同?线程id到底是什么?

2. pthread共享库

  • Linux没有真正的线程,只有轻量级进程的概念,所以Linux OS只会提供轻量级进程创建的系统调用,不会直接提供线程创建的接口。
  • 但是我们学习操作系统时,一直接触的就是线程,难道为了学Linux,还要再花功夫专门学一下轻量级进程吗?这显示是不合理的,所以Linux的解决方案是,在用户层和系统层之间,加入了一层软件层,封装了轻量级进程的调用接口,对上直接提供线程的控制接口。
  • 这层中间的软件层,就是pthread原生线程库。

在这里插入图片描述

Linux为什么不直接在系统层设计线程,还非要搞一个轻量级进程,最后还必须用一个库来封装成线程?

  • 首先,直接在系统层设计线程,技术上是绝对可以实现的。Linux之所以没有采取这种方式,是因为它觉得轻量级进程,是自己系统的一大特色,而不是缺点。其中一个特色就是,轻量级进程复用PCB,调度方法也复用进程的调度方法。
  • 一个完整的Linux系统,默认是携带pthread原生线程库的,如果没有,那么这个Linux系统是残缺的。

3. 线程管理

  • 我们之前所使用的线程控制接口,全部都不是系统接口,而是原生线程库pthread提供的接口。
  • 既然可以同时存在多个线程,那么肯定也需要对线程进行管理(先描述,再组织),在Linux中,这个管理工作是交给pthread库来执行的(先提出这个概念,一步步慢慢理解)。
  • 线程要有自己独立的硬件上下文和独立的栈结构,默认地址空间中的栈,由主线程使用。
  • 事实上,Linux系统也提供了直接创建轻量级进程的接口:
    • child_stack参数允许用户显示的传栈地址,也就是指定分配的栈地址空间。

在这里插入图片描述

  • pthread_create底层封装的就是这个接口,也会自动帮我们分配该轻量级进程的栈地址空间。pthread库中会有专门的结构化数据struct tcb,将轻量级进程封装为线程,来存储每个LWP的相关信息,包括该LWP所占用的栈空间地址。tcb需要和LWP一一对应,所以也要存储该LWP对应的轻量级进程id
  • pthread作为共享库,一次加载后,被所有进程所共享,所以各个进程可以看到多个用户启动的所有线程。

4. tid

在这里插入图片描述

  • 每创建一个线程,pthread中就会记录一个线程属性集合,包括struct pthread,线程局部存储,线程栈。这个线程属性集合,就可以理解成是tcb
  • 所谓tid,线程id,实际上就是各个属性集合的地址,所以tid的本质是一个地址。
  • 这个属性集合中,还存有类似void *ret的数据,用来保存线程的返回值。这个返回值可以通过pthread_join方法获取。所以线程的返回值并不是存储在系统中的,而是存储在库中的。

5. 站在语言角度,理解pthread

  • C++11的多线程,本质上,就是对原生线程库的封装。
#include <iostream>
#include <thread>// 这是一个简单的函数,将作为线程执行的任务
void print_numbers(int n) {for (int i = 0; i < n; ++i) {std::cout << "Number: " << i << std::endl;}
}int main() {std::cout << "Main thread starting." << std::endl;// 创建一个线程,传递函数print_numbers和它的参数std::thread t(print_numbers, 10); // 这里的10是print_numbers函数的参数// 在主线程中做一些事情std::cout << "Main thread doing other work." << std::endl;// 等待线程t完成t.join();std::cout << "Main thread finished." << std::endl;return 0;
}
  • 上面这部分代码,在Linux上跑,就会去调用Linux的原生线程库。在Windows上跑,就会调用Windows的原生线程库。同样的代码可以跑在不同的操作系统上,这就是语言层面的可移植性。

6. 线程的局部存储

  • 全局变量本身是被所有线程所共享的,这不难理解。全局变量存储在数据段,而所有线程共享一个地址空间。
  • 给全局变量前面加上__thread修饰(这实际上是一个编译选项),每个线程就会在自己的tcb中,多开辟一块空间,存储这个变量。这样每个线程实际上就拥有了自己独立的全局变量。这就是线程的局部存储。

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

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

相关文章

[免费]SpringBoot+Vue校园社团管理系统(优质版)【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的SpringBootVue校园社团管理系统(优质版)&#xff0c;分享下哈。 项目视频演示 【免费】SpringBootVue校园社团管理系统(优质版) Java毕业设计_哔哩哔哩_bilibili 项目介绍 随着信息技术的迅速发展&#x…

114 二叉树展开为链表

解题思路&#xff1a; \qquad 题目中已经明确&#xff0c;要用先序遍历的顺序展开&#xff0c;那么就需要按照“中-左-右”的顺序遍历二叉树&#xff0c;剩下的问题在于如何在遍历过程中完成链表指针的更新。 \qquad 由于“右子节点”需要链接到左子树最后一个节点之后&#…

Kylin麒麟操作系统 | Nginx服务部署

目录 一、理论储备1. Nginx概述2. Nginx与Apache的区别3. Nginx的服务配置 二、任务实施任务1 Nginx的编译安装1. Server配置2. 客户端测试 任务2 Nginx反向代理1. Server1配置2. Server2配置3. 客户端测试 一、理论储备 1. Nginx概述 Nginx是一个轻量级的web服务器&#xff…

类OCSP靶场-Kioptrix系列-Kioptrix Level 1

一、前情提要 二、实战打靶 1. 信息收集 1.1. 主机发现 1.2. 端口扫描 1.3 目录爆破 1.4. 敏感信息 2.根据服务搜索漏洞 2.1. 搜索exp 2.2. 编译exp 2.3. 查看exp使用方法&#xff0c;并利用 3. 提权 二、第二种方法 一、前情提要 Kioptrix Level是免费靶场&#x…

Golang囊地鼠gopher

开发知识点-golang 介绍红队专题-Golang工具Fscan简介主要功能ubuntu 安装windows 安装常用命令:项目框架源文件common目录Plugins目录Webscan目录入口点插件扫描类型爆破插件common.ScantypeWebtitle函数webpoc扫描POC 执行CEL-GO 实践CEL指纹识别免杀源码特征参考链接红队专…

快速上手Spring注解、SpringAop

1 Spring 注解的使用 1 IOC 的概念 IOC&#xff08;Inversion of Control&#xff09;&#xff1a;控制反转。 使用对象时&#xff0c;由主动new产生对象转换为由外部提供对象&#xff0c;此过程中对象创建控制权由程序转移到外部&#xff0c;此思想称为控制反转。通俗的讲就…

数据地图怎么做?推荐这款数据可视化地图生成器

在数字化与信息化高速发展的今天&#xff0c;企业迎来了前所未有的发展机遇&#xff0c;规模迅速扩张&#xff0c;市场版图不断延伸。然而&#xff0c;伴随着这种快速的发展&#xff0c;一个不容忽视的问题逐渐浮出水面——如何精准高效地掌握分布在各地的分公司、业务点乃至整…

鸿蒙项目云捐助第九讲鸿蒙App应用的捐助详情页功能实现

鸿蒙项目云捐助第九讲鸿蒙App应用的捐助详情页功能实现 这里接下来继续实现捐助详情页的布局页面&#xff0c;也就是当用户进入到分类页面后&#xff0c;点击分类的每一个商品就进入到捐助商品的详情页&#xff0c;这里的布局可以从下面的模板演化而来。 下面根据这个模板来进…

【数学】矩阵的逆与伪逆 EEGLAB

文章目录 前言matlab代码作用EEGLAB 中的代码总结参考文献 前言 在 EEGLAB 的使用中&#xff0c;运行程序时出现了矩阵接近奇异值&#xff0c;或者缩放错误。结果可能不准确。RCOND 1.873732e-20 的 bug&#xff0c;调查 EEGLAB 后发现是 raw 数据的问题。 matlab代码 A_1 …

华为HarmonyOS NEXT 原生应用开发:鸿蒙中组件的组件状态管理、组件通信 组件状态管理小案例(好友录)!

文章目录 组件状态管理一、State装饰器1. State装饰器的特点2. State装饰器的使用 二、Prop装饰器&#xff08;父子单向通信&#xff09;1. Prop装饰器的特点2. Prop装饰器的使用示例 三、Link装饰器&#xff08;父子双向通信&#xff09;1. Link装饰器的特点3. Link使用示例 四…

Pytorch | 对比Pytorch中的十种优化器:基于CIFAR10上的ResNet分类器

Pytorch | 对比Pytorch中的十种优化器&#xff1a;基于CIFAR10上的ResNet分类器 CIFAR10数据集ResNet提出背景网络结构特点工作原理优势 代码实现分析utils.pymain.py导入必要的库设备选择与数据预处理定义加载训练集和测试集主函数部分训练部分测试部分 结果10种优化器对应的训…

Linux系统操作03|chmod、vim

上文&#xff1a; Linux系统操作02|基本命令-CSDN博客 目录 六、chmod&#xff1a;给文件设置权限 1、字母法 2、数字法&#xff08;用的最多&#xff09; 七、vim&#xff1a;代码编写和文本编辑 1、启动和退出 1️⃣启动 2️⃣退出 2、vim基本操作 六、chmod&#x…

徐州数字孪生工业互联网可视化技术,赋能新型工业化智能制造工厂

#徐州数字孪生工业互联网#在当下智能制造的热潮之下&#xff0c;徐州作为中国制造业的重要基地&#xff0c;正积极拥抱数字化转型&#xff0c;通过数字孪生工业互联网可视化技术&#xff0c;赋能新型工业化智能制造工厂&#xff0c;引领制造业向更高效、更智能、更绿色的方向发…

C# 探险之旅:第二十四节 - 类型class基础,一场“类”似的奇妙冒险

嘿&#xff0c;勇敢的探险家们&#xff01;欢迎来到C#王国的“类”似奇妙冒险&#xff01;今天&#xff0c;我们要深入探索一个神秘而强大的领域——class&#xff08;类&#xff09;。想象一下&#xff0c;class就像C#世界里的一块魔法土地&#xff0c;每块土地上都能孕育出独…

(五)机器学习 - 数据分布

数据分布&#xff08;Data Distribution&#xff09;是指数据在不同值或值区间内的分布情况&#xff0c;它描述了数据点在整个数据集中是如何分散或集中的。数据分布可以通过多种方式来分析和表示&#xff0c;包括图形和数值方法。 常见的数据分布特征和描述数据分布的方法&…

基于stm32的红外测温系统设计(论文+源码)

1总体方案设计 本课题为基于STM32的红外测温系统设计&#xff0c;在此将系统架构设计如图3.1所示&#xff0c; 整个系统包括STM32F103单片机&#xff0c;红外测温模块MLX90614&#xff0c;显示模块OLED12864&#xff0c;蜂鸣器以及按键等构成&#xff0c;在功能上&#xff0c;…

排序算法(5):归并排序

问题 排序 [30, 24, 5, 58, 18, 36, 12, 42, 39] 归并排序 归并排序采用分治法&#xff0c;将序列分成若干子序列&#xff0c;每个子序列有序后再合并成有序的完整序列。 在数组排序中&#xff0c;如果只有一个数&#xff0c;那么它本身就是有序的。如果有两个数&#xff0…

JSSIP的使用及问题(webRTC,WebSockets)

简介 项目中有一个需要拨打电话的功能&#xff0c;要求实时的进行音频接听&#xff0c;并且可以在电话接听或者挂断等情况下做出相应的操作。jssip作为一个强大的实现实时通信的javascript库&#xff0c;这不门当户对了嘛。 jssip&#xff08;官网&#xff1a; JsSIP - the J…

DP3复现代码运行逻辑全流程(六)—— gen_demonstration_adroit.sh 演示生成与可视化

用于生成演示、培训和评估的脚本都在 Scripts/ 文件夹中 DP3 通过 gen_demonstration 生成演示&#xff0c;即训练数据&#xff0c;例如: bash scripts/gen_demonstration_adroit.sh hammer 这将在 Adroit 环境中生成锤子任务的演示。数据将自动保存在 3D-Diffusion-Policy/…

Python常用字符串排序●sorted()函数--一行语句简洁实现

在Python等编程中&#xff0c;时常会用到字符串排序。 今天在这里只讲讲最常用的Python字符串排序。 同时&#xff0c;只讲sorted()函数方法。 给定一个字符串列表&#xff1a; sl [共和国, 中国, 中华人民共和国, 大中华, 人民共和国]。 第一种排序方法是不使用任何参数…