Linux:进程池制作(基于匿名管道和命名管道两个版本)

news2024/9/23 15:32:12

Linux:进程池制作 & 匿名管道 & 命名管道

  • 前言
  • 一、匿名管道制作进程池
  • 一、进程池框架
  • 二、创建管道、创建进程、工作进程执行任务
    • 2.1 创建管道、创建进程
  • 2.2 工作进程执行任务
  • 三、主进程向子进程发送任务
    • 3.1 任务封装
    • 3.2 主进程向子进程发送任务
  • 四、回收资源
  • 五、所有源代码
    • 5.1源码
    • 5.2 运行结果(包含调试信息)
  • 六、命名管道实现进程池
    • 6.1 实现思路
    • 6.2 源码

前言

 创建进程是有时间成本的。当计算机要执行任务时才创建进程,势必会影响执行任务的性能。所以我们可以通过提前创建一批进程,当有任务需要被执行时直接喂给这些进程即可。我们把这些提前创建好的进程称为进程池!!
 下面我们会通过一个主进程(父进程)通过匿名管道和一批工作进程(子进程)进行通信。父进程通过不断派发任务给子进程,子进程通过读取管道文件中的任务码来执行对应的任务,从而模拟进程池的整个行为!!

一、匿名管道制作进程池

一、进程池框架

 父进程创建一批子进程,并建立单向通信通道。我们这里规定父进程向匿名管道中一次只能写4字节数据,子进程一次只能从管道文件中读取4字节数据。由于匿名管道的性质,父进程只需向目标子进程所对应的管道中发送任务码即可。如果管道文件中存在数据,子进程会不断读取任务码,执行对应的任务;否则子进程进入阻塞状态,等待主进程向管道中写入数据!

 但主进程需要知道向那个管道中发送任务码,向那个匿名管道中进行写入,以及子进程信息。即我们需要对管道进行管理!先描述,在组织!这里我们通过一个类对管道进行描述,其中保存着:主进程控制写端文件描述符、工作进程id和名字!由于我们需要执行快速随机访问,所以选择vector进行组织管理!所以这个进程池的制作大致框架如下:

【描述结构体】:

#define NUM 5//任务进程个数

int number = 1;//管道编号
class channel
{
public:
    channel(int fd, pid_t id)
    :ctrlfd(fd)
    ,workerid(id)
    {
        name = "channle-" + std::to_string(number++);
    }
    
public:
    int ctrlfd;
    pid_t workerid;
    std::string name;
};

【框架】:

int main()
{
    std::vector<channel> channels;//用于管理管道
    
    //1、创建管道,创建进程,工作进程工作方式
    CreaterChannel(&channels);
    
    //2、主进程向工作进程发送任务
    // 这里特殊设计,我们通过g_always_loop来判断主进程是一直发送任务,还是发送指定次后就结束退出
    const bool g_always_loop = true;
    SendTask(channels, !g_always_loop, 10);
    
    // 回收资源: 关闭写端,子进程自动退出
    ReleaseChannels(channels);
    return 0;
}

二、创建管道、创建进程、工作进程执行任务

2.1 创建管道、创建进程

 我们可以通过父进程循环NUM次,每次先创建管道,然后创建子进程。此时关闭父进程和子进程中不需要的读写段,建立单向通信通道。此时就可以建立如下关系:
在这里插入图片描述
 但上述简单关闭管道文件的读写段会存在问题的!我们需要特殊处理:
在这里插入图片描述
 上述这些多余的指向管道读端是fork创建子进程时,子进程继承父进程的信息之一(红线)!所以我们每次创建出的子进程还需将所有继承父进程多余的读端全部关闭,否则无法回收子进程导致内存泄漏!!
 其中最简单的解决办法就是:我们将父进程的写端文件描述符全部记录下来,每次创建出子进程时,子进程所继承的多余读写信息已经全部保存。我们只需依次将其关闭即可!!

void CreaterChannel(std::vector<channel> *channels)
{
    std::vector<int> old;//记录子进程继承的多余读端
    for(int i = 0; i < NUM; i++)//创建NUM个工作进程
    {
        // 1. 创建管道
        int pipefd[2];
        int n = pipe(pipefd);
        assert(n == 0);
        (void)n;//防止编译器报警

        //2. 创建子进程
        pid_t id = fork();
        if(id < 0)
        {
            perror("fork");
            return;
        }

        // 3. 形成单向通信
        if(id == 0)//子进程,工作进程
        {
            if(!old.empty())//将子进程继承父进程的多余读端全部关闭
            {
                for(auto rfd : old)
                {
                    close(rfd);
                }
            }
            close(pipefd[1]);
            dup2(pipefd[0], 0);//将读端重定向到标准输入
            work();//子进程执行任务
            exit(0);
        }

        //父进程,主进程
        close(pipefd[0]);
        channels->push_back(channel(pipefd[1], id));//主进程对应写端和工作进程信息
        old.push_back(pipefd[1]);
    }
}

2.2 工作进程执行任务

 当管道中有数据时,子进程只需读取相关任务码,然后不断执行即可!但如果此时父进程退出时,由于匿名管道的特性,read的返回值会被设为0,此时子进程在进行读取就没有任何意义了,子进程退出!!(检查任务码和执行任务码实现后续会统一分析)

void work()
{
    while(1)
    {
        int code = 0;
        ssize_t n = read(0, &code, sizeof(code));
        if(n == 0)
        {//主进程写端退出,子进程也退出
            break;
        }
        else if(n > 0)
        {
            if(!init.CheckSafe(code))//检查任务码是否合法
                continue;
            init.RunTask(code);//执行任务码
        }
        else
        {
            //nothing
        }
    }
}

三、主进程向子进程发送任务

3.1 任务封装

 下面我们仅仅通过一些打印数据来子进程待执行的所有模拟任务

【待执行任务】:

void Download()
{
    std::cout << "我是一个下载任务" << std::endl;
}


void Printflog()
{
    std::cout << "我是一个打印日志任务" << std::endl;
}

void PushVideoStream()
{
    std::cout << "我是一个推送视频流任务" << std::endl;
}

【任务封装】:
 我们通过包装器function将上述指针函数进行统一。同时我们向管道中读取和写入的是任务码,所以下面我们给出了相应的任务码,并将上述如何通过vector容器进行管理,下标对应任务码信息,并封装成了类!
 除此之外,还提供选择任务接口(随机选择)、检查任务码是否合理、任务码转对应任务名、运行特定任务码对应任务等接口!
 具体如下:

using task_t = std::function<void()>;

class Init
{
public:
    //任务码
    static const int g_Download_code = 0;
    static const int g_Printflog_code = 1;
    static const int g_PushVideoStream_code = 2;
    std::vector<task_t> tasks;
public:
    Init()
    {
        tasks.push_back(Download);
        tasks.push_back(Printflog);
        tasks.push_back(PushVideoStream);
        srand(time(nullptr));
    }

    int SelectTask()//选择任务接口
    {
        return rand() % tasks.size();
    }

    std::string CodeToName(int code)//任务码转对应任务名
    {
        switch(code)
        {
        case 0:
            return "Download";
        case 1:
            return "Printflog";
        case 2:
            return "PushVideoStream";
        default:
            return "Nothing";
        }
    }


    bool CheckSafe(int code)//检查任务码是否合理
    {
        return code >= 0 && code < tasks.size();
    }

    void RunTask(int code)//行特定任务码对应任务
    {
        tasks[code]();
    }
};

Init init;

3.2 主进程向子进程发送任务

 考虑到子进程完成任务的负载均衡,我们通过循环依次向每一个子进程发送任务码,较为平均的将任务分配给子进程!

 向子进程发送任务码,首先要确定待发送的任务,信道。然后将任务码写入信道!
 前面已经提到过,我们这里所设计的发送任务接口支持:死循环一直发送任务、发送指定次数任务后退出!所以最后我们需要判断是否发送任务需求结束,退出!具体如下:

void SendTask(const std::vector<channel> &channels, bool flag, int num)
{
	int pos = 0;//所选择信道所在数组位置下标
    while(true)
    {
        // 1. 选择任务
        int commit = init.SelectTask();
        if(init.CheckSafe(commit))
        {
            // 2. 选择信道, 发送任务码
            channel c = channels[pos++];
            pos %= channels.size();
            write(c.ctrlfd, &commit, sizeof(commit));
        }

        //判断是否需要退出
        if(!flag)
        {
            if(--num == 0)
                break;
        }
    }
}

四、回收资源

 当父进程发送任务信息退出后,我们仅需将写端关闭即可此时子进程执行read的返回值为0。子进程此时就能识别到写端已经退出,此时子进程也退出!最后让父进程等待子进程,防止子进程僵尸导致内存泄漏即可!!

void ReleaseChannels(std::vector<channel> &channels)
{
    for(auto & c : channels)
    {
        close(c.ctrlfd);
        pid_t rid = waitpid(c.workerid, nullptr, 0);
        if(rid == -1)
        {
            perror("waitpid");
            return;
        }
        std::cout << "wait " << c.name << " " << c.workerid << " success" << std::endl;
    }
}

五、所有源代码

emsp;到处为止,进程池的简单制作到处就结束了,下面是所有源码,其中还包含调试代码!

5.1源码

【ProcessPool.chh】

#pragma once 

#include <iostream>
#include <functional>
#include <vector>
#include <ctime>

using task_t = std::function<void()>;

void Download()
{
    std::cout << "我是一个下载任务" << std::endl;
}


void Printflog()
{
    std::cout << "我是一个打印日志任务" << std::endl;
}

void PushVideoStream()
{
    std::cout << "我是一个推送视频流任务" << std::endl;
}


class Init
{
public:
    //任务码
    static const int g_Download_code = 0;
    static const int g_Printflog_code = 1;
    static const int g_PushVideoStream_code = 2;
    std::vector<task_t> tasks;
public:
    Init()
    {
        tasks.push_back(Download);
        tasks.push_back(Printflog);
        tasks.push_back(PushVideoStream);
        srand(time(nullptr));
    }

    int SelectTask()
    {
        return rand() % tasks.size();
    }

    std::string CodeToName(int code)
    {
        switch(code)
        {
        case 0:
            return "Download";
        case 1:
            return "Printflog";
        case 2:
            return "PushVideoStream";
        default:
            return "Nothing";
        }
    }


    bool CheckSafe(int code)
    {
        return code >= 0 && code < tasks.size();
    }

    void RunTask(int code)
    {
        tasks[code]();
    }
};

Init init;

【processPool.cpp】

#include <iostream>
#include <unistd.h>
#include <cassert>
#include <cerrno>
#include <cstdlib>
#include <string>
#include <vector>
#include <cstdio>
 #include <sys/wait.h>
#include "ProcessPool.chh"

#define NUM 5//任务进程个数

int number = 1;//管道编号
class channel
{
public:
    channel(int fd, pid_t id)
    :ctrlfd(fd)
    ,workerid(id)
    {
        name = "channle-" + std::to_string(number++);
    }
    
public:
    int ctrlfd;
    pid_t workerid;
    std::string name;
};

void work()
{
    while(1)
    {
        int code = 0;
        ssize_t n = read(0, &code, sizeof(code));
        if(n == 0)
        {//主进程写端退出,子进程也退出
            break;
        }
        else if(n > 0)
        {
            if(!init.CheckSafe(code))
                continue;
            init.RunTask(code);
        }
        else
        {
            //nothing
        }
    }
}

void CreaterChannel(std::vector<channel> *channels)
{
    std::vector<int> old;//记录主进程的读端
    for(int i = 0; i < NUM; i++)
    {
        // 1. 创建管道
        int pipefd[2];
        int n = pipe(pipefd);
        assert(n == 0);
        (void)n;//防止编译器报警

        //2. 创建子进程
        pid_t id = fork();
        if(id < 0)
        {
            perror("fork");
            return;
        }

        // 3. 形成单向通信
        if(id == 0)//子进程,工作进程
        {
            if(!old.empty())//将子进程继承父进程多余读端全部关闭
            {
                for(auto rfd : old)
                {
                    close(rfd);
                }
                for(auto id : old)
                {
                    std::cout << "creater quit id:" << id << " " << std::endl;
                }
            }
            close(pipefd[1]);
            dup2(pipefd[0], 0);//将读端重定向到标准输入
            work();
            exit(0);
        }

        //父进程,主进程
        close(pipefd[0]);
        channels->push_back(channel(pipefd[1], id));
        old.push_back(pipefd[1]);
    }
}

//Debug
void PrintChannel(const std::vector<channel> &channels)
{
    std::cout << channels.size() << std::endl;
    for(int i = 0; i < channels.size(); i++)
    {
        std::cout << "name:" << channels[i].name << ", 写端描述符:" << channels[i].ctrlfd 
                  <<", 工作进程id:" << channels[i].workerid << std::endl;
    }
    std::cout << "Creater success" << std::endl;
}


void SendTask(const std::vector<channel> &channels, bool flag, int num)
{
    int pos = 0;//所选择信道所在数组位置下标
    while(true)
    {
        // 1. 选择任务,返回任务码
        int commit = init.SelectTask();
        if(init.CheckSafe(commit))
        {
            // 2. 选择信道, 发送任务码
            channel c = channels[pos++];
            pos %= channels.size();
            write(c.ctrlfd, &commit, sizeof(commit));

            // Debug
            std::cout << "select channel: " << c.name << ", send task:" << init.CodeToName(commit) << "[" << commit << "]"
                      << ", workerid:" << c.workerid << std::endl;
        }

        //判断是否需要退出
        if(!flag)
        {
            if(--num == 0)
                break;
        }

    }
}

void ReleaseChannels(std::vector<channel> &channels)
{
    for(auto & c : channels)
    {
        close(c.ctrlfd);
        pid_t rid = waitpid(c.workerid, nullptr, 0);
        if(rid == -1)
        {
            perror("waitpid");
            return;
        }
        std::cout << "wait " << c.name << " " << c.workerid << " success" << std::endl;
    }
}

int main()
{
    std::vector<channel> channels;
    //创建管道,创建进程
    CreaterChannel(&channels);
    std::cout << "Creater end" << std::endl;
    PrintChannel(channels);

    //主进程向工作进程发送任务
    const bool g_always_loop = true;
    SendTask(channels, !g_always_loop, 10);
    
    // 回收资源: 关闭写端,子进程自动退出
    //sleep(3);
    ReleaseChannels(channels);
    return 0;
}

5.2 运行结果(包含调试信息)

在这里插入图片描述

六、命名管道实现进程池

6.1 实现思路

 命名管道实现进程池,我们可以创建多个命名管道,同时父进程打开写端,子进程打开读端!(注意此时创建的子进程还是会出现基础多余的读端文件描述符。所以子进程在打开命名管道读端前,同样需要先将多余的写端描述符关闭)
 发生任务码,和子进程执行任务和匿名管道实现方式一样,就不多说了。
 至于回收资源,那就更简单了。我们只需将命名管道写端描述符关闭,此时子进程会获取的读端退出信息(即read返回值为0),将管道读端也关闭!!最后父进程等待回收子进程,防止子进程僵尸即可!!

6.2 源码

【ProcessPool.cpp】

#include <iostream>
#include <unistd.h>
#include <cassert>
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include <cstdio>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "ProcessPool.chh"

#define NUM 5//任务进程个数


class fifo
{
public:
    fifo(std::string str, pid_t id, int fd)
    :FILENAME(str.c_str())
    ,workerid(id)
    ,ctrlfd(fd)
    { }
    
public:
    const char *FILENAME;
    pid_t workerid;
    int ctrlfd;
};

int Work()
{
    int code = 0;
    while(true)
    {
        ssize_t n = read(0, &code, sizeof(code));
        std::cout << "read size:" << n << std::endl;
        if(n == 0)
        {
            std::cout << "write quit, mee to!" << std::endl;
            break;
        }
        else if(n > 0)
        {
            init.RunTask(code);
        }
        else
        {
            std::cerr << "errno: " << errno << "errstring: " << strerror(errno) << std::endl;
            return 1;
        }
    }
    return 0;
}

bool Createrfifo(std::vector<fifo> *fifos)
{
    std::vector<int> old;
    for(int i = 1; i <= NUM; i++)
    {
        std::string filename = "fifo-" + std::to_string(i);
        int s = mkfifo(filename.c_str(), 0644);
        if(s == -1)
        { 
            std::cout << "errno:" << errno << " strerror" << strerror(errno) << std::endl;
            return false;
        }

        pid_t id = fork();
        if(id == 0)//child
        {
            if(!old.empty())
            {
                for(auto& fd : old)
                    close(fd);
            }
            int rfd = open(filename.c_str(), O_RDONLY);
            dup2(rfd, 0);
            Work();
            std::cout << "read close!!!!!!!!!!!!!!!!!!!!!" << std::endl;
            close(rfd);
            exit(0);
        }

        //parent
        int wfd = open(filename.c_str(), O_WRONLY);
        fifos->push_back(fifo(filename, id, wfd));
        old.push_back(wfd);
    }
    return true;
}

void SendCommit(const std::vector<fifo> &fifos, int flag, int num)
{
    int pos = 0;
    while(true)
    {
        // 1. 选择任务
        int code = init.SelectTask();
        if(init.CheckSafe(code))
        {
            //2. 发送任务码
            write(fifos[pos].ctrlfd, &code, sizeof(code));
            pos = (pos + 1) % fifos.size();
        }
        // 3.判断是否退出
        if(!flag)
        {
            if(--num == 0)
            {
                break;
            }
        }
    }
    std::cout << "write finish!!!" << std::endl;
}

void Releasefifo(std::vector<fifo> &fifos)
{
    std::cout << "start release" << std::endl;
    for(auto& fifo : fifos)
    {
        std::cout << "colse wfd: " <<  fifo.ctrlfd << std::endl;
        close(fifo.ctrlfd);
        pid_t id = waitpid(fifo.workerid, nullptr, 0);
    }
    std::cout << "release success!" << std::endl;
}

int main()
{
    std::vector<fifo> fifos;
    // 1.创建命名管道,进程
    if(!Createrfifo(&fifos))
        return 0;
    sleep(2);
    for(const auto& fifo : fifos)
        std::cout << fifo.FILENAME <<":" << fifo.workerid << ":" << fifo.ctrlfd << std::endl;

    // 2.发送任务
    sleep(2);
    bool g_always_loop = true;
    SendCommit(fifos, !g_always_loop, 10);

    // 3. 回收资源
    sleep(2);
    Releasefifo(fifos);
    return 0;
}

【ProcessPool.chh】

#pragma once 

#include <iostream>
#include <functional>
#include <vector>
#include <ctime>

using task_t = std::function<void()>;

void Download()
{
    std::cout << "我是一个下载任务" << std::endl;
}


void Printflog()
{
    std::cout << "我是一个打印日志任务" << std::endl;
}

void PushVideoStream()
{
    std::cout << "我是一个推送视频流任务" << std::endl;
}


class Init
{
public:
    //任务码
    static const int g_Download_code = 0;
    static const int g_Printflog_code = 1;
    static const int g_PushVideoStream_code = 2;
    std::vector<task_t> tasks;
public:
    Init()
    {
        tasks.push_back(Download);
        tasks.push_back(Printflog);
        tasks.push_back(PushVideoStream);
        srand(time(nullptr));
    }

    int SelectTask()
    {
        return rand() % tasks.size();
    }

    std::string CodeToName(int code)
    {
        switch(code)
        {
        case 0:
            return "Download";
        case 1:
            return "Printflog";
        case 2:
            return "PushVideoStream";
        default:
            return "Nothing";
        }
    }


    bool CheckSafe(int code)
    {
        return code >= 0 && code < tasks.size();
    }

    void RunTask(int code)
    {
        tasks[code]();
    }
};

Init init;

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

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

相关文章

昇思Mindspore25天学习打卡Day21:Diffusion扩散模型

昇思Mindspore25天学习打卡Day21&#xff1a;Diffusion扩散模型 1 模型简介1.1 什么是Diffusion1.2 扩散模型实现原理1.2.1 Diffusion前向过程1.2.2 Diffusion逆向过程1.2.3 U-Net神经网络预测噪声 2 构建Diffusion模型2.1 位置向量2.2 ResNet/ConvNeXT块2.3 Attention模块2.4 …

JVM原理(二三):JVM虚拟机线程安全的实现方法

1. 互斥同步 互斥同步(MutualExclusion&Synchronization)是一种最常见也是最主要的并发正确性保障手段。同步是指在多个线程并发访问共享数据时&#xff0c;保证共享数据在同一个时刻只被一条(或者是一些&#xff0c;当使用信号量的时候)线程使用。而互斥是实现同步的一种…

防御---001

一、实验拓扑二、要求 1&#xff0c;DMZ区内的服务器&#xff0c;办公区仅能在办公时间内(9:00 - 18:00)可以访问&#xff0c;生产区的的设备全天可以访问. 2&#xff0c;生产区不允许访问互联网&#xff0c;办公区和游客区允许访问互联网 3,办公区设备10.0.2.10不允许访问DMZ…

数据结构(Java):集合类LinkedList集合类Stack

1、集合类LinkedList 1.1 什么是LinkedList LinkedList的底层是一个双向链表的结构&#xff08;故不支持随机访问&#xff09;&#xff1a; 在LinkedList中&#xff0c;定义了first和last&#xff0c;分别指向链表的首节点和尾结点。 每个节点中有一个成员用来存储数据&…

第十一篇——兵势篇:部下强不强,责任在领导

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 领导力的体现&#xff0c;也是要通过区分不同层次程度的内容来体现最终的…

Bash ——shell

Bash作为用户与操作系统之间的接口&#xff0c;让用户通过命令行输入各种指令来控制和操作计算机系统。 shell的两种解释&#xff1a; 1.linux命令解释器 Terminal 终端 ——》shell命令 ——》 Linux kernel &#xff08;内核&#xff09; Linux内核的作用&#xff1a; 1.…

Java中实现一维数组逆序交换的完整解决方案

引言 ❤❤点个关注吧~~编程梦想家&#xff08;大学生版&#xff09;-CSDN博客 在日常编程中&#xff0c;处理数组时经常会遇到需要逆序交换数组元素的情况。逆序交换即是将数组的第一个元素与最后一个元素交换&#xff0c;第二个元素与倒数第二个元素交换&#xff0c;依此类推…

Internet Download Manager6.42最新下载器互联网冲浪小能手们!

今天我要来种草一个超级棒的宝贝——Internet Download Manager&#xff08;简称 IDM&#xff09;。这个小家伙简直是下载界的“速度与激情”代言人&#xff0c;让我彻底告别了等待的日子。&#x1f389; IDM马丁正版下载如下: https://wm.makeding.com/iclk/?zoneid34275 …

【最强八股文 -- 计算机网络】【快速版】TCP 与 UDP 头部格式

目标端口和源端口: 应该把报文发给哪个进程包长度: UDP 首部的长度跟数据的长度之和校验和: 为了提供可靠的 UDP 首部和数据而设计&#xff0c;接收方使用检验和来检查该报文段中是否出现差错 源端口号和目的端口号: 用于多路复用/分解来自或送到上层应用的数据。告诉主机报文段…

自动气象站的主要功能优势

在科技日新月异的今天&#xff0c;我们生活的方方面面都受到了科技的影响。其中&#xff0c;自动气象站作为气象观测领域的重要一环&#xff0c;不仅提升了气象数据的准确性和时效性&#xff0c;还为我们的日常生活、农业生产、灾害预防等提供了重要的数据支持。 自动气象站概述…

【GIS开发小课堂】WebGIS开发必学开源框架Openlayers,附赠视频教程、电子书、笔记源码

WebGIS开发之Openlayers 当前&#xff0c;WebGIS开发热门程度越来越高&#xff0c;市场招聘供需比处于较为紧张的状态。 常见的WebGIS开源框架有&#xff1a;OpenLayers、Leaflet、MapBox、MapFish、GeoServer、GeoEXT、MapInfo等。公司最希望求职者具备至少一种框架开发技能…

数字信号处理及MATLAB仿真(5)——z变换

采样的其他概念咱们后面再慢慢的讲述吧&#xff0c;先把z变换的程序给大家展示一下&#xff0c;总的来说呢&#xff0c;就用一个函数——ztran就行了。在 MATLAB 中&#xff0c;可以使用 ztrans 函数来进行 Z 变换。ztrans 函数用于对离散时间信号或系统进行 Z 变换&#xff0c…

MySQL Undo Log

总结自bojiangzhou undo log称为撤销日志或回滚日志。在一个事务中进行增删改操作时&#xff0c;都会记录对应的 undo log。在对数据库进行修改前&#xff0c;会先记录对应的 undo log&#xff0c;然后在事务失败或回滚的时候&#xff0c;就可以用这些 undo log 来将数据回滚到…

java 实现Comparable接口和实现Comparator接口排序的区别

Comparable接口 作用&#xff1a; Comparable接口是在类的内部实现的&#xff0c;用于指定类的默认比较规则。当一个类实现了Comparable接口时&#xff0c;它必须实现compareTo方法&#xff0c;该方法用于定义对象之间的自然顺序。 实现方式&#xff1a; 实现Comparable接口的…

【Go系列】 Go语言的入门

为什么要学习Go 从今天起&#xff0c;我们将一同启程探索 Go 语言的奥秘。我会用简单明了的方式&#xff0c;逐一讲解 Go 语言的各个知识点&#xff0c;帮助你从基础做起&#xff0c;一步步深化理解。不论你之前是否有过 Go 语言的接触经验&#xff0c;这个系列文章都将助你收获…

农业采摘--RGBD数据转point cloud

一、RGBD图像转点云数据的步骤 将RGBD图像转点云数据常包含五个步骤&#xff1a; 1. 图像采集&#xff1a; 使用RGBD相机同时捕获颜色&#xff08;RGB&#xff09;和深度&#xff08;Depth&#xff09;信息。颜色记录了场景的彩色视觉信息&#xff0c;而深度图像记录了场景中每…

个人面试总结

写在前面&#xff1a;以下是自己在拟录用后回顾总结的了一下当时面试题目&#xff0c;把标答写了出来&#xff0c;供以后复习所使用&#xff0c;希望大家理性食用~~ 预祝大家都能找到心仪的工作 笔试题目&#xff1a; 1.1. java中Collection和Collections的区别 Collection…

Ae After Effects2024 for Mac 视频处理软件

Mac分享吧 文章目录 效果一、准备工作二、开始安装1、Anticc简化版安装1.1双击运行软件&#xff0c;安装1.2 解决来源身份不明的开发者问题1.3 再次运行软件&#xff0c;即可进行AntiCC安装 2. Ae2024安装2.1 打开 Ae 2024 安装包组2.2 将 Ae 安装包拖至桌面2.3 安装 Ae2024 &…

0708,LINUX目录相关操作

主要是冷气太足感冒了&#xff0c;加上少吃药抗药性差&#xff0c;全天昏迷&#xff0c;学傻了学傻了 cat t_chdir.c #include <stdio.h> #include <unistd.h> #include <error.h> #include <errno.h> #include <sys/stat.h>int main(int argc…

PyTorch实现BERT预训练模型转化指南

huggingface官方的介绍&#xff1a; https://huggingface.co/transformers/converting_tensorflow_models.html 直接用命令行 把箭头处路径改为自己放原有tf版本预训练模型的路径 回车后会有一大堆提示&#xff0c;然后发现路径下多了一个bin文件&#xff0c;加上原本的config…