【Linux】线程控制

文章目录

    • 线程的概念
      • Linux下的进程
      • Linux下的线程
      • 进程再理解
      • Linux线程和接口的认识
      • 代码验证
      • 二级页表
    • 页表
    • 线程的优点
    • 线程的缺点
      • 线程异常
    • 线程的用途
    • 进程和线程的关系
    • 线程控制
      • 线程
      • 线程ID和LWP
      • 线程等待
      • 线程终止
      • 线程分离
    • 线程ID及进程地址空间布局

线程的概念

我们知道,进程在各自独立的地址空间中运行,进程之间共享数据需要用mmap或者进程间通信机制,本节我们学习如何在一个进程的地址空间中执行多个线程。有些情况需要在一个进程中同时执行多个控制流程,这时候线程就派上了用场,比如实现一个图形界面的下载软件,一方面需要和用户交互,等待和处理用户的鼠标键盘事件,另一方面又需要同时下载多个文件,等待和处理从多个网络主机发来的数据,这些任务都需要一个“等待-处理”的循环,可以用多线程实现,一个线程专门负责与用户交互,另外几个线程每个线程负责和一个网络主机通信。

以前我们讲过,main函数和信号处理函数是同一个进程地址空间中的多个控制流程,多线程也是如此,但是比信号处理函数更加灵活,信号处理函数的控制流程只是在信号递达时产生,在处理完信号之后就结束,而多线程的控制流程可以长期并存,操作系统会在各线程之间调度和切换,就像在多个进程之间调度和切换一样。

Linux下的进程

进程:PCB+mm_struct+页表+MMU+物理内存

先将代码加载到虚拟内存,然后通过预加载对代码进行一点点地映射到物理内存。

Linux下的线程

Linux当中其实没有线程这一概念,我们把轻量级进程叫做线程,线程是运行在进程当中的执行流,一个进程最少要有一个执行流,线程也是CPU调度的最小单位。

如下为线程的简略示意图:

在Linux当中,没有创建线程专属的结构体,而是对进程PCB进行稍作修改,也就是轻量级进程。

各线程共享以下进程资源和环境:

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

但如下资源是每个线程各有一份的:

  • 线程id
  • 上下文,包括各种寄存器的值、程序计数器和栈指针
  • 栈空间
  • errno变量
  • 信号屏蔽字
  • 调度优先级

进程再理解

曾经说进程就是一个task_struct+进程地址空间+页表+MMU+物理内存,这么说也没错,因为进程至少有一个执行流,当执行流为1的时候就成立。

但现在又知道,一个进程当中会存在多个执行流,因此,如下一整块才被称之为进程。

进程是分配系统资源的实体。

线程是CPU调度的最小单位。

Linux线程和接口的认识

在Linux中线程是用进程模拟实现的 所以说Linux中不会给我们提供线程的操作接口 (这里解释下 其实Linux不是没有能力去提供这些操作接口 而是它想要保持一个相对自由的状态给用户) 而是给我们提供了一个在同一个进程地址空间中创建PCB的方法 分配给资源指定的PCB

但是作为一个用户来说 使用这种方法的学习成本太高了 我们更需要一个完整的线程库

所以说一些应用级的开发工程师就在应用层对于轻量级的Linux接口进行封装成为了我们经常使用的原生线程库

代码验证

#include <pthread.h>
 
int pthread_create(pthread_t *thread,
    const pthread_attr_t *restrict attr,
    void *(*start_routine)(void*), void *restrict arg);

Compile and link with -pthread.

以下是各个参数的具体含义:

  1. thread:指向新创建的线程标识符的指针。线程创建后,其标识符将被存储在此指针所指向的位置。
  2. attr:指向线程属性的对象。若不关心线程的属性,可以将其设为NULL。
  3. start_routine:指向新线程的入口点函数的指针。此函数必须是静态链接的,且不能返回任何值。
  4. arg:传递给新线程入口点函数的参数。这个参数可以为空。

注意:pthread_create函数返回一个整型值,表示线程的创建状态。如果线程创建成功,它将返回0;如果出现错误,它将返回一个非零值。

其它线程可以调用pthread_join得到start_routine的返回值,类似于父进程调用wait(2)得到子进程的退出状态。

pthread_create成功返回后,新创建的线程的id被填写到thread参数所指向的内存单元。我们知道进程id的类型是pid_t,每个进程的id在整个系统中是唯一的,调用getpid()可以获得当前进程的id,是一个正整数值。线程id的类型是thread_t,它只在当前进程中保证是唯一的,在不同的系统中thread_t这个类型有不同的实现,它可能是一个整数值,也可能是一个结构体,也可能是一个地址,所以不能简单地当成整数用printf打印,调用pthread_self()可以获得当前线程的id。

#include <iostream>
#include <pthread.h>
#include <unistd.h>

int num = 0;

using namespace std;

void *test(void *args)
{
    while (1)
    {
        cout << "线程ID:" << pthread_self() << " "<< "num:" << num++ << endl;
        sleep(1);
    }
}

int main(void)
{
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, test, (void *)"hello");
    pthread_create(&tid2, NULL, test, (void *)"hello");
    sleep(1);
    cout << "tid1:" << tid1 << endl;
    cout << "tid2:" << tid2 << endl;
    while (1)
    {
        sleep(1);
    }

    return 0;
}

可以看到,这里定义的全局变量num并没有像多线程一样发生修改的时候就会重新映射页表,使得进程之间的资源独立,而是多个线程之间共享了这个num

#include <iostream>
#include <pthread.h>
#include <unistd.h>

//int num = 0;

using namespace std;

void *test(void *args)
{
    int num=0;
    while (1)
    {
        cout << "线程ID:" << pthread_self() << " "<< "num:" << num++ << endl;
        sleep(1);
    }
}

int main(void)
{
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, test, (void *)"hello");
    pthread_create(&tid2, NULL, test, (void *)"hello");
    sleep(1);
    cout << "tid1:" << tid1 << endl;
    cout << "tid2:" << tid2 << endl;
    while (1)
    {
        sleep(1);
    }

    return 0;
}

但如果定义在函数体内部,可以发现num就不共享了,这是由于线程都有自己的栈空间,线程也有自己的私有数据。

我们可以通过ps -aL命令来查看线程。

PID代表的是Process ID(进程ID),LWP代表的是Light Weight Process(轻量级进程)。这两个概念并不相同,但在一些场景下有所关联。

  1. PID:PID是指系统中唯一的标识一个进程的一个数字,通常用来标识某个具体的进程。当创建了一个新的进程时,系统会给它分配一个唯一的PID。
  2. LWP:LWP是指轻量级进程,它与普通进程相比具有较低的开销。通常来说,一个普通的进程会有若干个LWP组成,每个LWP都负责执行某一部分代码或任务。

ps -aL命令中,PID表示的是整个进程的标识,而LWP表示的是进程中的某个具体的执行实体。例如,如果一个进程有多个LWP,那么在ps -aL命令中,每个LWP都会有一个自己的PIDLWP ID,以此来区分不同LWP之间的执行状态。

这里的PID:20486PIDLWP怎么是一样的呢?

因为这个是主线程,另外两个就是我们创建的子线程。

二级页表

在讲二级页表之前先说一下一级页表。

假如我们没有页表,所有的内存都是段式访问,而段式内存访问有一个缺点:

我们的进程C需要11M的内存,但是由于空闲区域F1和F2都不能满足进程C需要的内存,同时由于进程A和B都是活跃进程,因此不可以被腾出,于是进程C必须等待进程A或进程B腾出相应的内存空间,但是这种等待是不可控的。

F1和F2两块区域的和是足够进程C的内存,但由于我们为进程C分配的内存是连续的,因此这种段式内存的利用率是低下的。

为了解决这种分段模式下进程的线性地址等同于物理地址问题,我们必须要将线性地址和物理地址解绑,解绑以后线性地址连续,但物理地址可以不连续。

这就需要借助分页机制。

页表

(MIT6.S081)页表

线程的优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比 线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速IO操作结束的同时 程序可执行其他的计算任务
  • 计算密集型应用 为了能在多处理器系统上运行 将计算分解到多个线程中实现
  • IO密集型应用 为了提高性能 将IO操作重叠 线程可以同时等待不同的IO操作
  • 计算密集型:执行流的大部分任务,主要以计算为主。比如加密解密、大数据查找等。
  • IO密集型:执行流的大部分任务,主要以IO为主。比如刷磁盘、访问数据库、访问网络等。

线程的缺点

  • 性能损失: 一个很少被外部事件阻塞的计算密集型线程往往无法与其他线程共享同一个处理器 如果计算密集型线程的数量比可用的处理器多 那么可能会有较大的性能损失 这里的性能损失指的是增加了额外的同步和调度开销 而可用的资源不变

假设有一个单核处理器上的程序,其中有三个计算密集型任务A、B和C。如果这三个任务同时运行,则它们都需要等待处理器空闲出来才能继续运行。如果任务A正在运行,则任务B和C都需要等待任务A完成才能继续运行,反之亦然。这就是所谓的同步和调度开销。 为了减少这种情况带来的开销,可以将任务A、B和C分别放在不同的处理器上运行。这样,它们就可以并行地执行,从而减少了同步和调度开销。但是,如果只有一个处理器,则这种方法无效。 因此,为了获得最佳的性能,应根据实际情况确定最适合的线程数量。如果任务是计算密集型的,则线程数等于可用的处理器数量;如果是I/O密集型的任务,则线程数应小于可用的处理器数量。健壮性降低: 编写多线程需要更全面更深入的考虑 在一个多线程程序里 因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的 换句话说 线程之间是缺乏保护的。

  • 缺乏访问控制: 进程是访问控制的基本粒度 在一个线程中调用某些OS函数会对整个进程造成影响
  • 编程难度提高: 编写与调试一个多线程程序比单线程程序困难得多

线程异常

  • 如果某一个线程出现除零错误、野指针等问题,整个进程都会崩溃
  • 线程是进程的执行分支,线程出现了异常,就类似进程出现异常,进而触发信号,整个进程都会出错。

线程的用途

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

进程和线程的关系

线程控制

线程

#include <pthread.h>
 
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
  • 返回值:成功返回0,失败返回错误号,以前学过的系统函数都是成功返回0,失败返回-1,错误号会保存在全局变量errno当中,而pthread库的函数都是通过返回值返回错误号,虽然每个线程都有一个errno,但这是为了兼容其它函数接口提供的,pthread库本身并不使用它,通过返回值返回错误码更加清晰。
  • thread:是一个输出型参数,创建成功的线程ID会写入这个变量当中。
  • attr:用于设置线程的特殊属性,NULL表示使用默认的线程属性。
  • start_routine:创建线程后要执行的函数。
  • arg:函数的参数。

在一个线程中调用了pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传递给pthread_create()的函数指针start_routine决定,start_routine会接受一个参数,就是arg,参数类型为void*,这个指针具体是什么类型由调用者自己决定,void*可以接受任何类型的指针,通过强制类型转换可以转化为自己想要的类型。start_routine的返回类型也是void*,这个指针的含义同样是由调用者自己定义。当start_routine函数返回的时候,这个线程就退出了。其他线程可以调用pthread_join()来获取start_routine的返回值,就类似于父进程可以调用wait()得到子进程的退出状态。

下面我们让主线程调用pthread_create函数创建一个新线程,此后新线程就会跑去执行自己的新例程,而主线程则继续执行后续代码。

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include<string.h>

using namespace std;

void *test(void *args)
{
    int num=0;
    while (1)
    {
        cout << "线程ID:" << pthread_self() << " "<< "num:" << num++ << endl;
        sleep(1);
    }
}

int main(void)
{
    pthread_t tid1;
    int err=pthread_create(&tid1, NULL, test, (void *)"hello");
    if(err!=0)
    {
        fprintf(stderr, "can't create thread: %s\n", strerror(err));
        exit(1);
    }
    sleep(1);
    while (1)
    {
        cout<<"我是主线程,线程ID:"<<pthread_self()<<endl;
        sleep(2);
    }
    return 0;
}

运行代码后可以看到,新线程每隔一秒执行一次打印操作,而主线程每隔两秒执行一次打印操作。

while :; do ps axj | head -1 ; ps axj |grep pthread | grep -v grep ; echo "------------------------------------------------------------
---"; sleep 1 ; done; echo "-------------------------------------------------

通过如上命令可以查看进程,可以看到始终只有一个进程

ps -aL

通过如上命令可以查看线程,可以看到有两个线程,一个主线程,一个子线程。

线程ID和LWP

pthread_self()拿到的是用户级的线程ID,而LWP是内核级的线程ID,类似于文件描述符和inode之间的关系。

线程等待

#include <pthread.h>
 
int pthread_join(pthread_t thread, void **value_ptr);
  • thread:被等待的线程ID(用户级)。
  • value_ptr:线程退出时的退出码信息。
  • 如果thread线程通过return返回,value_ptr所指向的单元里存放的是thread线程函数的返回值。
  • 如果thread线程被别的线程调用pthread_cancel异常终止掉,value_ptr所指向的单元里存放的是常数PTHREAD_CANCELED
  • 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。

如果对thread线程的终止状态不感兴趣,可以传NULLvalue_ptr参数。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include<string>
#include<string.h>
#include <sys/types.h>
using namespace std;

struct info
{
    string name;
    int return_val;
};

void *thr_fn1(void *arg)
{
    printf("thread 1 returning\n");
    return (void*)1;
}
 
void *thr_fn2(void *arg)
{
    printf("thread 2 exiting\n");
    pthread_exit((void *)2);
}
 
void *thr_fn3(void *arg)
{
    while(1) {
        printf("thread 3 writing\n");
        sleep(1);
    }
}
 
int main(void)
{
    pthread_t   tid;
    void* tret;
 
    pthread_create(&tid, NULL, thr_fn1, NULL);
    pthread_join(tid, &tret);
    printf("thread 1 exit code %d\n", (int*)tret);
 
    pthread_create(&tid, NULL, thr_fn2, NULL);
    pthread_join(tid, &tret);
    printf("thread 2 exit code %d\n", (int*)tret);
 
    pthread_create(&tid, NULL, thr_fn3, NULL);
    sleep(3);
    pthread_cancel(tid);
    pthread_join(tid, &tret);
    printf("thread 3 exit code %d\n", (int*)tret);
 
    return 0;
}

可以看到,通过返回值返回的线程其tret都是返回的值,而是由pthread_cancel()取消线程的,返回值都是PTHREAD_CANCELED的整形。

在Linux的pthread库中常数PTHREAD_CANCELED的值是-1。可以在头文件pthread.h中找到它的定义:

#define PTHREAD_CANCELED ((void *) -1)
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include<string>
#include<string.h>
#include <sys/types.h>
using namespace std;

struct info
{
    string name;
    int return_val;
};

void *thr_fn1(void *arg)
{
    printf("thread 1 returning\n");
    return (void*)1;
}
 
void *thr_fn2(void *arg)
{
    printf("thread 2 exiting\n");
    exit(-1);
}
 
void *thr_fn3(void *arg)
{
    while(1) {
        printf("thread 3 writing\n");
        sleep(1);
    }
}
 
int main(void)
{
    pthread_t   tid;
    void* tret;
 
    pthread_create(&tid, NULL, thr_fn1, NULL);
    pthread_join(tid, &tret);
    printf("thread 1 exit code %d\n", (int*)tret);
 
    pthread_create(&tid, NULL, thr_fn2, NULL);
    pthread_join(tid, &tret);
    printf("thread 2 exit code %d\n", (int*)tret);
 
    pthread_create(&tid, NULL, thr_fn3, NULL);
    sleep(3);
    pthread_cancel(tid);
    pthread_join(tid, &tret);
    printf("thread 3 exit code %d\n", (int*)tret);
 
    return 0;
}

将代码稍作修改,如果我先终止线程2,那么整个进程都会退出,线程3也不会被执行了。

线程终止

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

  • 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit
  • 一个线程可以调用pthread_cancel终止同一进程中的另一个线程。
  • 线程可以调用pthread_exit终止自己。
#include <pthread.h>
 
void pthread_exit(void *value_ptr);

value_ptrvoid *类型,和线程函数返回值的用法一样,其它线程可以调用pthread_join获得这个指针。

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

线程分离

一般情况下,线程终止之后,其终止状态会一直保持到其他线程调用pthread_join来获取线程的终止状态。但如果线程被detach(分离)了,一旦线程之中之后直接会被操作系统回收掉。

不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL。对一个尚未detach的线程调用pthread_joinpthread_detach都可以把该线程置为detach状态,也就是说,不能对同一线程调用两次pthread_join,或者如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。

#include <pthread.h>

int pthread_detach(pthread_t thread);
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
 
void *thr_fn1(void *arg)
{
    pthread_detach(pthread_self());
    printf("thread 1 returning\n");
    sleep(3);
    return (void *)1;
}
 
int main(void)
{
    pthread_t tid;
    void* tret;
 
    pthread_create(&tid, NULL, thr_fn1, NULL);
    pthread_join(tid, &tret);
    printf("thread 1 exit code %d\n", (int*)tret);
    while(1)
    {
        
    }
    return 0;
}

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

线程之间到底共享了哪些资源?

线程之间到底哪些资源是私有的?

我们知道,一个程序会经历预处理、编译、汇编、链接这四个过程,而链接就是在链接动静态库。

也就是说线程会共享动态库中的所有代码。

一个进程的虚拟地址空间一般可以大致划分为代码区(text)、只读数据区(rodata)、初始化数据区(data)、为初始化数据区(bss)、堆(heap)、共享内存区(.so,mmap的地方)、栈(stack)、内核区(kernel)。

对于 Linux 进程或者说主线程,其 stack 是在 fork 的时候生成的,实际上就是复制了父亲的 stack 空间地址,然后写时拷贝 (cow) 以及动态增长
然而对于主线程生成的子线程而言,其 stack 将不再是这样的了,而是事先固定下来的。
线程栈不能动态增长,一旦用尽就没了,这是和生成进程的 fork 不同的地方。

线程(非主线程)的栈的大小是固定的,其会在空闲的堆(堆顶附近自顶向下分配)或者是空闲栈(栈底附近自底向上分配),因此线程栈局部函数中分配的变量是存放到各自分配的栈空间,因此可以说是线程私有的,又因为该线程栈的边界是设定好的,线程栈之间有以小块guardsize用来隔离保护各自的栈空间,一旦另一个线程踏入到这个隔离区,就会引发段错误,因此该线程栈的大小的固定的。

主线程从进程栈分配空间,大小并不是固定的,如果分配空间大于进程栈空间,那么直接运行时出现段错误。

通过ulimit -a可以看到,栈的大小是8192kb,也就是8M

从 heap 的顶部向下分配。
ps -ajax| grep pthread 查看 pid
cat /proc/[pid]/maps 这个显示进程映射了的内存区域和访问权限。
可以看到:在 heap 下面连续的几个属性为 rw-p 的地址大小刚好都为 8192kb。并且每个都在边界穿插了一个大小为 1000H(4096kb) 的边界空间。

从 stack 底部向上分配
ulimit -s unlimited 设置 stack size 为 unlimited,注意虽然设置了stack size为无限,但是实际上其并不是无限的,而也是固定大小的线程栈,大小为1mb。
然后 cat /proc/[pid]/maps 查看虚拟地址空间的映射。
可以看到,这种情况下线程栈是分配在 stack 底附近,自底向上生长的。

当每增加一个线程,就会在栈或者堆区创建一个结构体,这个结构体来源于动态库。

对于主线程的代码或者变量,所有线程都是共享的。

void thread(void* var) {
    int* p = (int*)var;
    *p = 2;
}
 
 
int main() {
    int a = 1;
    pthread_t tid;
    
    pthread_create(&tid, NULL, thread, (void*)&a);
    return 0;
}

而对于线程的私有栈,数据是不共享的。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

全新小权云黑系统

小权云黑管理系统 V1.0 功能如下&#xff1a; 1.添加骗子&#xff0c;查询骗子 2.可添加团队后台方便审核用 3.在线反馈留言系统 4.前台提交骗子&#xff0c;后台需要审核才能过 5.后台使用光年UI界面 6.新增导航列表&#xff0c;可给网站添加导航友链 7.可添加云黑类型收录 8.…

基于STC12C5A60S2系列1T 8051单片的IIC总线器件模数芯片PCF8591实现模数转换应用

基于STC12C5A60S2系列1T 8051单片的IIC总线器件模数芯片PCF8591实现模数转换应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍IIC总线器件模数芯片PCF8591介绍通过I…

注解方式优雅的实现 Redisson 分布式锁

1前言 日常开发中&#xff0c;难免遇到一些并发的场景&#xff0c;为了保证接口执行的一致性&#xff0c;通常采用加锁的方式&#xff0c;因为服务是分布式部署模式&#xff0c;本地锁Reentrantlock和Synchnorized这些就先放到一边了&#xff0c;Redis的setnx锁存在无法抱保证…

asp.net健身会所管理系统sqlserver

asp.net健身会所管理系统sqlserver说明文档 运行前附加数据库.mdf&#xff08;或sql生成数据库&#xff09; 主要技术&#xff1a; 基于asp.net架构和sql server数据库 功能模块&#xff1a; 首页 会员注册 教练预约 系统公告 健身课程 在线办卡 用户中心[修改个人信息 修…

Modbus RTU 使用教程(modbus教程、modbus协议)

参考文章&#xff1a;这节课带你吃透Modbus通信协议 文章目录 Modbus RTU 使用教程第1部分&#xff1a;Modbus RTU 协议概述什么是Modbus RTU协议Modbus RTU的特点工作原理 第2部分&#xff1a;硬件和软件要求硬件要求软件要求 第3部分&#xff1a;Modbus RTU数据结构数据帧格式…

android适配鸿蒙系统开发

将一个Android应用迁移到鸿蒙系统需要进行细致的工作&#xff0c;因为两者之间存在一些根本性的差异&#xff0c;涉及到代码、架构、界面等多个方面的修改和适配。以下是迁移工作可能涉及的一些主要方面&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专…

各品牌PLC元件在modbus内区域

1台达&#xff1a; 输出在0区&#xff0c; 040961是在 0区 0xA000~0xA0FF 【Y0~Y377】 输入在1区&#xff0c;124577是在 1区 0x6000~0x60FF 【X0~X377】 M寄存器0区&#xff0c;0000001是 0区&#xff0c;0x000~0x1FFF 【M0~M8191】 D寄存器4区&#xff0c;400000…

CTFhub-RCE-过滤运算符

检查网页源代码发现如果用户输入 || &#xff08;逻辑或操作符&#xff09; 或者是 & &#xff08;按位与操作符&#xff09;&#xff0c;就不会执行。直接进行测试&#xff0c;在 URL 后输入本地 IP 进行 ping 命令测试&#xff0c;发现可以正常回显。检查网页源代码发现如…

2018年五一杯数学建模B题商业银行人民币贷款规模分配及盈利问题解题全过程文档及程序

2019年五一杯数学建模 B题 商业银行人民币贷款规模分配及盈利问题 原题再现 商业银行贷款投放的简单模型是&#xff1a;从客户端吸收存款&#xff0c;缴存法定准备金&#xff08;法定准备金率&#xff1a;大型金融机构15.5%&#xff0c;中小金融机构12%&#xff1b;法定准备金…

Spring Boot Actuator:自定义端点

要在Spring Boot Actuator中实现自定义端点&#xff0c;可以按照以下步骤进行操作&#xff1a; 1.创建一个自定义端点类 该类需要使用Endpoint注解进行标记&#xff0c;并使用Component注解将其作为Spring Bean进行管理。 package com.example.highactuator.point;import lo…

flowable消息事件

一&#xff1a;启动事件 定义消息。 引用消息。 <startEvent id"msgStart" name"消息启动事件" isInterrupting"true"><messageEventDefinition messageRef"myMsgStart"></messageEventDefinition> </startE…

机器学习第6天:线性回归模型正则化

文章目录 机器学习专栏 正则化介绍 岭回归 岭回归成本函数 核心代码 示例 Lasso回归 Lasso回归损失函数 核心代码 弹性网络 弹性网络成本函数 核心代码 结语 机器学习专栏 机器学习_Nowl的博客-CSDN博客 正则化介绍 作用&#xff1a;正则化是为了防止模型过拟合…

es使用客户端,“grunt” 不是内部或外部命令,多种解决方法

”grunt“不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件。 4、问题排查 查看node的安装根目录 npm root -g 在运行grunt -version还是不行 网上找了很多&#xff0c;给出正确解决方案的没几个&#xff0c;所以自己摸索&#xff0c;最后确定了加环境变量的解…

Python实验项目7 :tkinter GUI编程

&#xff08;1&#xff09;利用tkinter 制作界面&#xff0c;效果图如下&#xff1a; from tkinter import * # winTk() for i in range(1,20):Button(width5,height10,bg"black" if i%20 else"white").pack(side"left") win.geometry("8…

Android 屏幕适配

目录 一、为什么要适配 二、几个重要的概念 2.1 屏幕尺寸 2.2 屏幕分辨率 2.3 屏幕像素密度 2.4 屏幕尺寸、分辨率、像素密度三者关系 三、常用单位 3.1 密度无关像素(dp) 3.2 独立比例像素&#xff08;sp&#xff09; 3.3 dp与px的转换 四、解决方案 4.1 今日头条…

Docker之DockerFile解析

DockerFile解析 是什么 Dockerfile是用来构建Docker镜像的文本文件&#xff0c;是由一条条构建镜像所需的指令和参数构成的脚本。 概述 官网 https://docs.docker.com/engine/reference/builder/ 构建三步骤 编写Dockerfile文件 docker build命令构建镜像 docker run依镜像运…

李沐的学习Pytorch环境配置

https://github.com/Miraclelucy/dive_into_deep_learning/tree/main 上面是别人的笔记 可以学一下。 如果没有梯子&#xff0c;按照清华源配置 清华源conda配置 最好下载 1.11版本torch那一套 然后装d2l版本可以装 pip install d2l0.17.6然后可以用 http://localhost:8889/…

DolphinScheduler V3.1.8 海豚调度器【概述、安装、功能介绍、项目运用、邮箱预警设置】轻松拿捏!

概述 Apache DolphinScheduler 是一个分布式易扩展的可视化 DAG 工作流任务调度开源系统。适用于企业级场景&#xff0c;提供了一个可视化操作任务、工作流和全生命周期数据处理过程的解决方案。 Apache DolphinScheduler 旨在解决复杂的大数据任务依赖关系&#xff0c;并为应…

Diffusion Models CLIP

Introduction to Diffusion Models 生成模型 主要指的是无监督学习中的生成模型&#xff0c;在无监督学习中的主要任务是让机器学习给定的样本&#xff0c;然后生成一些新的东西出来。比如&#xff1a;给机器看一些图片&#xff0c;能够生成一些新的图片出来&#xff0c;给机器…

.locked勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复

导言&#xff1a; 随着科技的不断发展&#xff0c;网络空间的威胁也日益增加。其中&#xff0c;.locked勒索病毒作为一种恶意软件&#xff0c;给用户的数据安全带来了极大的威胁。本文91数据恢复将介绍.locked勒索病毒的特点&#xff0c;以及如何有效恢复被它加密的数据文件&a…