【进程间通信 之 通信的建立】

news2024/11/28 5:46:55

目录:

  • 前言
  • 进程间通信的目的
  • 进程间通信的方式
  • 管道
    • 1.匿名管道
      • 简单示例1 - 消息传输
      • 五个特性
      • 四种场景
      • 简单示例2 - 进程控制
      • 对管道的深入理解
    • 2.命名管道
    • 简单示例3 -- 不相关进程间通信
  • system V
    • 共享内存
      • 简单示例4 - 通知事件+消息传输
  • 总结

前言

打怪升级:第69天
在这里插入图片描述

进程间通信的目的

数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。


进程间通信的方式

  1. 管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
  2. 命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
  3. 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  4. 共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
  5. 信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  6. 套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
  7. 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

该部分内容来源:进程间通讯的7种方式

本文主要讲解管道、命名管道以及共享内存三个部分,希望可以给朋友们提供帮助。


管道

1.匿名管道

管道,通常指匿名管道,是 UNIX 系统IPC最古老的形式,下文中所说的管道和匿名管道是同一个,都是管道。
它可以在两个相关联的进程之间创建一个管道(Pipe),进程可以通过管道进行单向通信。

在 pipe 中,数据是按照字节流的形式传输的,即发送进程将数据写入管道的一端,接收进程从管道的另一端读取数据。管道可以看作是一条单向的、先进先出(FIFO)的队列,进程可以在队列的两端进行操作:写入数据的进程称为管道的写入端,读取数据的进程称为管道的读取端。

  • 命令

在这里插入图片描述
在这里插入图片描述
指令含义:查看我们当前所在路径,并将路径信息通过 管道 传递给 wc 指令,统计行数。

管道,可以给多个指令建立联系,使得多个指令可以进行数据传输,
上方的指令我想大家都是用过的,那么下面我们就来了解一下在语言层面通过系统调用建立匿名管道的方法。

  • 系统调用
    在这里插入图片描述
    管道也称为匿名管道,创建匿名管道的方法就是使用系统调用 pipe,参数很简单 :一个整形数组,或者说一个整形指针。
    函数的声明提示我们 pipefd数组有两个元素,其中这两个元素是两个fd(文件描述符),其中pipefd[0]表示读端pipefd[1]表示写端
    从这里我们就可以看出:管道是属于文件系统的,那么我们有了文件描述符,之后的操作就和对文件的操作一样。

简单示例1 - 消息传输

使用 pipe 进行进程间通信的步骤如下:

  1. 调用 pipe 函数创建一个管道,并返回两个文件描述符:一个用于读取数据,一个用于写入数据。
  2. 创建一个子进程,它可以通过继承父进程的文件描述符来访问管道。
  3. 在父进程中关闭管道的读取端(如果不需要读取数据)或关闭管道的写入端(如果不需要写入数据)。
  4. 在父进程和子进程中分别使用管道的读取端和写入端进行通信。
  5. 在通信完成后,关闭管道的另一端以释放资源。
    下面是一个简单的示例程序,展示了如何使用 pipe 进行进程间通信:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include<cstring>
int main()
{
    int fd[2];
    pid_t pid;

    if (pipe(fd) < 0) 
    {
        perror("pipe error");
        exit(EXIT_FAILURE);
    }

    pid = fork();
    if (pid < 0) 
    {
        perror("fork error");
        exit(EXIT_FAILURE);
    } 
    else if (pid == 0) /* 子进程 */
    { 
        close(fd[0]); /* 关闭管道的读取端 */
        const char* ptrs= "Hello, parent!";
        write(fd[1], ptrs, strlen(ptrs)); /* 向管道写入数据 */
        close(fd[1]); /* 关闭管道的写入端 */
        exit(EXIT_SUCCESS);
    } 
    else /* 父进程 */
    { 
        char buffer[1024];
        close(fd[1]); /* 关闭管道的写入端 */
        read(fd[0], buffer, sizeof(buffer)); /* 从管道读取数据 */
        printf("Message from child: %s\n", buffer);
        close(fd[0]); /* 关闭管道的读取端 */
        exit(EXIT_SUCCESS);
    }
}

运行结果:
这里是引用

在这里插入图片描述

上述程序创建了一个管道,然后创建了一个子进程。在子进程中向管道写入一条消息,并在父进程中读取该消息并打印输出。值得注意的是,在使用管道进行通信时需要注意错误处理、关闭文件描述符等问题,以免出现资源泄漏或其他错误。

五个特性

  1. 单向通信 – 一种特殊的半双工 – 一方只能写,一方只能读
  2. 管道本质上是文件,因为文件的生命周期随进程,所以管道的生命周期随进程
  3. 管道只能用于具有血缘关系的进程间的通信 – 父子、兄弟 – 管道继承
  4. 在管道通信中,读取的次数与写入的次数不是严格匹配的,读写次数没有强相关 – 字节流
  5. 具有一定的协同能力,使 read 和 write 能够按照一定的步骤进行通信 – 同步与互斥
  6. 当要写入的数据不大于PIPE_BUF时,Linux将保证写入的原子性。

注:管道一般专指匿名管道,匿名管道只能用于具有血缘关系间的进程通信;
下方我们会遇到 FIFO,一般称它为 命名管道,可以实现两个不相关进程间的通信。

四种场景

  1. read 快于 write:如果我们读取完毕管道中的数据,如果对方不发,我们会进行等待
  2. write 快于 read:read会一次读取write多次写入的数据,如果管道写满就不能写入
  3. 关闭管道的read:OS判定write后面的写入无效,OS不会容忍低效率、无意义的事情占用CPU资源,会杀死write的进程 – 信号13:SIGPIPE;
  4. 关闭管道的write:read读取完管道中的数据,再次读取取得数据为0,退出。

简单示例2 - 进程控制

一个父进程同时对多个子进程发布任务。

  • mypipe.hpp
#include<iostream>
using namespace std;
#include<vector>

struct Person
{
  int _wfd; // 写文件描述符
  int _cid;  // 子进程id
};

void PrintLog()
{
  cout << "我正在执行打印日志的任务..." << endl;
}
void LoadVideo()
{
  cout << "我正在执行加载视频的任务..." << endl;
}
void WaitNetwork()
{
  cout << "我正在等待网络资源" << endl;
}                                                                          


typedef void(*FUNC)(); // 定义一个函数指针类型

class Task
{
  public:
  
    Task()
     {
      _taskArr.push_back(PrintLog);
      _taskArr.push_back(LoadVideo);
      _taskArr.push_back(WaitNetwork);
     }

  public:
   vector<FUNC> _taskArr; // 任务列表
};

  
  void Menu()
  {
    cout << "***********************" << endl;
    cout << "*1.打印日志 2.加载视频*" << endl;
    cout << "*3.等待网络 0.退出    *" << endl;                               
    cout << "***********************" << endl;
  }

  • test.cc
  #include"mypipe.hpp"
  #include<cstdlib>
  #include<cassert>
  #include<sys/types.h>
  #include<sys/wait.h>
  #include<unistd.h>
  
  const int cpCnt = 5; // 创建子进程个数
  
  void GetCommend(Task task)
  {
    while(true)
    {
      int commend;
      ssize_t rCnt = read(0, &commend, sizeof(commend));
      if(rCnt != sizeof(int) || commend == 0) break; // 读取到的应该是一个整数并且收到执行任务的指令
      cout << "我是子进程,我的pid是:" << getpid() <<", ";
      
      task._taskArr[commend-1]();
    }
 }
  
  void CtrlProess(Person* p, const Task task)
  {
    int commend;
    int i=0;
    do
    {
      Menu();
      cin >> commend;
      if(commend < 0 || commend > task._taskArr.size()) continue; // 保证选项合理
      write(p[i]._wfd, &commend, sizeof(commend));
      i = (i + 1) % cpCnt; // 各个子进程依次执行任务
      sleep(1);
      } while(commend);
      cout << "父进程退出" << endl;
  }                                                                          
  
  void Creat(Person* p, const Task& task)
  {
    for(int i=0; i<cpCnt; ++i)
    {
       // 创建管道
      int pipe_fd[2];
      assert(pipe(pipe_fd) != -1);
      //创建子进程
      pid_t id = fork();
      assert(id != -1);
  
      if(id == 0) // child
      {
        // 关闭不需要的fd
        //close(pipe_fd[1]); // 无法形成干净的管道
        for(int i = pipe_fd[0]+1; i<=pipe_fd[1]; ++i)
         close(i); // 如果有疑问可以分别画出子进程1,2,3的files结构体

        dup2(pipe_fd[0], 0); // 将文件标准输入重定向到该管道
        // 读取执行
        GetCommend(task);
        // 子进程退出
        close(pipe_fd[0]);
        exit(0);
      }
  
      // parent                                                              
    //关闭不需要的fd
      close(pipe_fd[0]);
      p[i]._wfd = pipe_fd[1];
      p[i]._cid = id;
    }
  }
  
  void WaitProc(Person* p)
  {
    for(int i=0; i<cpCnt; ++i)
    {
      close(p[i]._wfd);
      waitpid(p[i]._cid, nullptr, 0); // 等待子进程退出
    }
  
    cout << "等待子进程成功" << endl;
  }
  
// 一个父进程与多个子进程通过管道 -- 5 -- 父进程写 子进程读
  int main()
  {
    //创建管道与进程 -- 并且保存子进程与写文件信息
    Person p[cpCnt];
    Task task;
    Creat(p, task);
    //开始通信
    CtrlProess(p, task);
    // 关闭管道并且回收子进程资源
    WaitProc(p);
    return 0;
  }


对管道的深入理解

这里是引用

上方的代码我们可以不过多关注,但是有一个地方确是我们需要注意的:不需要的管道的关闭。
父进程同时管理多个子进程,按照我们的预期应该如上图所示,那么我们的代码是否可以达到我们的目的呢?

在这里插入图片描述

分析原因:在这里插入图片描述
我以为 vs 实际上
在这里插入图片描述

解决方法有三

  • 方案1

关闭写端 和 等待子进程分开来写,当我们关闭了所有写端后,子进程会从后往前一次退出并进入僵尸状态。
在这里插入图片描述

  • 方案2

我们可以从后往前倒着关闭匿名管道,这样类似于方案1.

在这里插入图片描述

  • 方案3

前两种方法虽说都可以达到我们的目的,但是,从根本上来说并没有达到所谓管道的条件 – 一方写来一方读
因此,我们可以在创建子进程的时候就将不必要存在的fd全部关闭掉,这样一来才能形成一个个干净的管道。
在这里插入图片描述


2.命名管道

对于匿名管道我们知道它只能用于具有血缘关系的进程直接通信,至于原因:因为它没有名字,除了一个家族内可以通过fd找到它,其他进程并不知道它的存在;
因此,为了实现毫不相干的进程之间的通信,我们有了FIFO,也称命名管道。
命名管道与管道的区别就是它拥有自己的名字,不同进程只要知道它的位置都可以找到它。

  • 指令

mkfifo filename
创建匿名管道,可以直接使用ls查看。
命名管道的删除可以使用 unlink命令,
同时,命名管道说到底还是文件,因此它的删除可以直接使用 rm。
在这里插入图片描述

  • 系统调用


参数一:命名管道路径名,创建文件的权限
这里就和文件类似,mode为八进制,分别为读、写、执行。

和管道不同的是:管道的生命周期是随进程的,但是命名管道我们可以提前手动创建与删除,OS不会自动帮我们回收,
因此,在使用完命名管道后我们需要手动删除它。
在这里插入图片描述
参数:文件路径

简单示例3 – 不相关进程间通信

  • msg.h // 存储一些公共信息
#include<iostream>
using namespace std;
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<cassert>
#include<cstring>

#define LOG "fifo"
#define MODE 0666
#define NUM 1024
  • user.cc // 用户端输入
#include"msg.h"

int main()
{
 // 打开文件 -- 只写
 int wfd = open(LOG, O_WRONLY);
 assert(wfd != -1);
  // 写入数据
  while(true)
  {
    char inquire[NUM] = {0};
    cout << "请输入消息# ";
    fgets(inquire, sizeof(inquire), stdin);
    inquire[strlen(inquire)-1] = 0; // 删除最后一个的 \n
    if(strcasecmp(inquire, "quit") == 0)  break;
    // 发送消息
    write(wfd, inquire, strlen(inquire));
  }

  cout << "user quit success." << endl;
  close(wfd);
  return 0;
}

  • server.cc // 服务端回显
#include"msg.h"

// 两个不同进程间的通信
int main()
{
  // 1.1 创建命名管道
  umask(0);
  int fifoRet = mkfifo(LOG, MODE);  
  assert(fifoRet != -1);
  (void)fifoRet; // 防止由于fifoRet只定义未使用而报警告
  cout << "create fifo success" << endl;
  // 打开文件 -- 只读
  int rfd = open(LOG, O_RDONLY);
  assert(rfd != -1);
  //1.2 读取数据 -- 进行反馈
  cout << "open fifo success" << endl;
  while(true)
  {
    char buf[NUM] = {0};
    ssize_t rCnt = read(rfd, buf, NUM - 1);
    if(rCnt == 0)
    {
      cout << "user quit, me too." << endl;
       break;
    }

    cout << "user# " << buf << endl;
  }
  //2.2关闭写端
  close(rfd);
  // 2.2 删除命名管道 -- nulink
  assert(unlink(LOG)==0);
  return 0;
}

在这里插入图片描述
有很重要的一点需要我们注意:当server端启动后一开始只打印了“创建成功”,直到user端也启动后才打印出“打开成功”,
也就是说:当只有server端是server是无法单独打开 fifo的,这点和pipe一样:OS不允许任何无效或低效率的事情发生,因为管道是用来传输数据的,但是当只有读端或者写端时无法进行通信,因此OS会将该进程阻塞,直到有其他进程以另一种方式打开管道才会给它们分配CPU资源。


system V

system V通信的三种方式:
System V 消息队列
System V 共享内存
System V 信号量

  • 指令
  1. 查看IPC通信结构:ipcs
  2. 删除IPC通信结构:ipcrm -m/q/s id

这里是引用

共享内存

  • 系统调用
  1. 创建共享内存 -> shm – shared memory

在这里插入图片描述
参数一:随机key值 – 由ftok函数生成
参数二:共享内存大小
参数三:shm打开方式以及shm权限设置

参数三的常用选项:IPC_CREAT , IPC_EXCL,IPC_RMID
IPC_CRETA:创建共享内存,如果存在(有相同的key)就使用已存在的,不存在就创建新的。
IPC_EXCL:创建共享内存时必须是新的,如果之前就存在就报错。(绝不将就)
ICP_RMID:根据共享内存的id号删除共享内存,在shmctl函数中使用。
返回值:创建成功返回 共享内存标识符 – shmid,创建失败返回-1.并且设置erron

在这里插入图片描述

ftok()函数使用给定路径名命名的文件的标识(它必须引用一个现有的、可访问的文件)

有效的8位proj_id(必须是非零的)来生成key_t类型的System V IPC密钥
简单说:文件名和proj_id 这两个参数都是可以随便给的,它们的作用就是生成一个新的key(类似于生成随机数)来唯一标识shm。
返回值:生成成功返回 key,生成失败返回-1并设置erron。

在这里插入图片描述

  • shmat:shm attach,触摸shm,和shm建立连接,返回值为shm的首地址,有了地址我们就可以将它当做数组来使用。
    参数一:shmid
    参数二:可以直接设为nullptr
    参数三:可以直接设为0
    shmat返回值:连接成功返回 shm地址 – shmaddr,失败返回-1,设置erron;

  • shmdt:shm detach ,脱离shm,与shm取消链接
    参数一:shm地址
    shmdt返回值:取消关联成功返回 0,失败返回-1,设置erron。

在这里插入图片描述
参数一:shmid
参数二:IPC_RMID ,删除shm
参数三:直接设为nullptr
返回值:删除成功返回0,失败返回-1并设置erron。

在这里插入图片描述
补充知识点:共享内存的映射在进程地址空间的共享区(堆栈之间),如果大家了解过动静态库的话就知道,动态库也是链接在这部分的。

简单示例4 - 通知事件+消息传输

  • server.cc

#include"msg.hpp"

int main()
{
    // 和shm建立联系
    Shm s(SERVER);
    // 使用FIFO控制读取
    // 打开读端
    int rfd = open(FIFONAME, O_RDONLY);
    // 开始通信
    int cnt = 10;
    while(cnt)
    {
        size_t rsize = read(rfd, &cnt, sizeof cnt);
        if(rsize == 4)
        {
            printf("user# %s\n", s._address);
        }
    }

  return 0;
}

  • user.cc
#include"msg.hpp"

int main()
{
    // 1. 和shm建立联系
    Shm s(USER);
    // 2. 使用FIFO通知server可以进行读取
    //2.1 创建fifo
    mkfifo(FIFONAME, MODE);
    // 打开写端
    int wfd = open(FIFONAME, O_WRONLY);
    // 3. 开始通信
    int cnt = 10;
    while(cnt--)
    {
        printf("输入: ");
        fflush(stdout);

        fscanf(stdin, "%[^\n]s", s._address); // 遇到非换行符字符全部读取 -- 也就是遇到换行符就结束
        getchar(); // 	“吃掉” 缓冲区中的换行符
        write(wfd, &cnt, sizeof cnt); // 每次录入信息后写入一个整数,通知server可以进行读取
    }

  return 0;
}
  • msg.hpp
#include<iostream>
using namespace std;
#include<cassert>
#include<cstring>
#include<string>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

#define SIZE 4096
#define LOG  "."
#define NUM 0x1234
#define MODE 0666
#define SERVER 0 // 服务标识
#define USER 1   // 用户标识
#define FIFONAME "f.fifo"
class Shm{

public:
    Shm(int name)
    :_name(name)
    {
        // 创建shm
        int key = GetKey();
        if(_name == SERVER)
            _shmid = CreateShm(key, SIZE);
        else
            _shmid = FindShm(key, SIZE);
        cout << "create shm success." << endl;
        // 建立连接
        _address = Attach(_shmid);
        cout << "attach shm success." << endl;
    }

    ~Shm()
    {
        //   取消链接
        Detach(_address);

        // server删除shm
        if(_name == SERVER)
        {
            DelShm(_shmid);
            cout << "free shm success." << endl;
        }
    }

    void ToHex(int n)
    {
        if(n)
        {
            ToHex(n / 16);
            cout << n % 16;
        }
    }

    int GetKey()
    {
        int key = ftok(LOG, NUM);
        assert(key != -1);
        return key;
    }

private:
    int GetShm(int key, int size, int cmd)
    {
        int shmid = shmget(key, size, cmd);
        assert(shmid != -1);
        return shmid;
    }

    //  创建共享内存 -- 返回shmid
    int CreateShm(int key, int size)
    {
        umask(0);
        return GetShm(key, size, IPC_CREAT | IPC_EXCL | MODE); // IPC_EXCL:我只要新的,否则我就报错
    }


    int FindShm(int key, int size) 
    {
         return GetShm(key, size, IPC_CREAT);  // 我用其他人的也可以
    }

    char* Attach(int shmid)
    {
        char* shmadd = (char*)shmat(shmid, nullptr, 0);
        return shmadd;
    }

    void Detach(char* shmadd)
    {
        assert(shmdt(shmadd) != -1);
    }

    void DelShm(int shmid)
    {
        assert(shmctl(shmid, IPC_RMID, nullptr) != -1);
    }

public:
    int _name;   // 用户区分
    int _shmid;  // shm标识
    char* _address; // shm地址

};

在这里插入图片描述

总结

在这里插入图片描述

  1. 匿名管道只能用于具有血缘关系的进程之间通信;
  2. 在命令行中通过管道连接的各个指令是兄弟关系,它们的父进程都为bash;
  3. 管道与命名管道都属于文件系统,数据缓冲区的大小随文件缓冲区,数据的流动只在文件缓冲区中进行;
  4. 命名管道的大小永远是0,文件中的数据不会刷新到磁盘(仅仅作为通信的中间体,没有必要保存数据);
  5. 由于共享内存创建之后就不需要依赖于OS,因此消息传输是最快的,而同时,因为消息传输时不经过OS的控制,所以通信过程是不安全的 – 没有同步与互斥(两个用户同时进行写入,造成数据覆盖,信息丢失)。
  6. 两种管道如果只存在读端或者写端会被OS强制关闭,shm则不会。
  7. 内存中的数据是以块为单位存储的,一块的大小为 4KB(4096B),因此当我们申请4096B时OS会给我们分配4096B,
    但是如果我们申请4097B时OS就会给我们分配 2 * 4096B的空间,不过即使给我们分配了这么多,我们也只能使用4097B。
  8. 共享内存映射在进程地址空间的共享区


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

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

相关文章

后摩尔时代 , 从有源相控阵天线走向天线阵列微系统

本文围绕高分辨率对地微波成像雷达对天线高效率、低剖面和轻量化的迫切需求 , 分析研究了有源阵列天线的特点、现状、趋势和瓶颈技术 , 针对对集成电路后摩尔时代的发展预测 , 提出了天线阵列微系统概念、内涵和若干前沿科学技术问题 , 分析讨论了天线阵列微系统所涉及的微纳尺…

完全集成的云备份和还原服务——NetApp Cloud Backup

完全集成的云备份和还原服务 NetApp Cloud Backup 提供无缝且经济高效的备份和还原功能&#xff0c;用于保护和归档云端和内部 ONTAP 数据。 为什么选择 NetApp Cloud Backup&#xff1f; 可轻松保护云端和内部 ONTAP 数据 NetApp Cloud Backup 提供无缝且经济高效的备份和还…

科大讯飞星火认知大模型怎么样?

“鉴于SparkDesk服务目前处于内部用户体验阶段&#xff0c;所有与SparkDesk相关的交互内容、技术参数等信息均被视为保密信息。您了解并同意&#xff0c;且有责任对这些保密信息严格保密&#xff0c;您不得以任何方式&#xff08;包括但不限于截图、录屏、拍照等&#xff09;披…

Linux shell编程常用命令(sort排序 uniq重复行 set +-x调试脚本 tr压缩替换字符 cut切片)

sort命令 排序 以行为单位对文件内容进行排序&#xff0c;也可以根据不同的数据类型来排序 比较原则是从首字符向后&#xff0c;依次按ASCII码值进行比较&#xff0c;最后将他们按升序输出。 sort [选项] 参数 cat file | sort 选项-n 按照数字进行排序 -r 反向排序 -u 等同于u…

【源码解析】SpringBoot整合AOP原理解析

AOP介绍 AOP&#xff08;Aspect Oriented Programming&#xff09;是基于切面编程的&#xff0c;可无侵入的在原本功能的切面层添加自定义代码&#xff0c;一般用于日志收集、权限认证等场景。 AOP基本概念 通知&#xff08;Advice&#xff09;: AOP 框架中的增强处理。通知…

L1:提示工程的关键原则

提示工程指南&#xff1a;关键原则 一、 环境配置 chatgpt使用有诸多限制&#xff0c;所以采用国产模型来代替&#xff0c;加载开源的chatGLM模型&#xff0c;使用ChatGLM-6b的INT8版本。 chatGLM6b在LLM匿名竞技场中的排名&#xff1a; import os import torch import war…

[学习笔记] [机器学习] 4. [下]线性回归(正规方程、梯度下降、岭回归)

6. 梯度下降和正规方程的对比 问题梯度下降正规方程学习率需要选择合适的学习率不需要求解特点需要多次迭代求解一次运算得出线性问题可以解决可以解决非线性问题可以解决不可以解决时间复杂度难以直接给出的&#xff08;受到初始值、学习率、迭代次数等多种因素的影响&#x…

学生如何使用chatGTP提升学习能力?

短短两三个月&#xff0c;ChatGPT炸圈范围越来越大&#xff0c;很快就从科技圈来到了教育界。前段时间&#xff0c;北密歇根大学的哲学教授Antony Aumann在批改论文的过程中发现一篇论文好到令人感到震惊。这篇论文逻辑严谨&#xff0c;措辞得当&#xff0c;结构清晰&#xff0…

《JavaEE初阶》Tomcat

《JavaEE初阶》Tomcat 文章目录 《JavaEE初阶》TomcatTomcat是什么下载Tomcat简单介绍Tomcat的文件使用tomcat部署静态页面启动tomcat 部署代码: Tomcat是什么 在学习tomcat之前,我们已经学习了HTTP协议,我们知道HTTP协议是应用层协议. HTTP的客户端是我们的网页和浏览器,而H…

MySQL新增时实现新增或更新操作

MySQL新增时数据重复则更新或不操作&#xff0c;不重复则新增 应用场景实现方案1. REPLACE INTO 语句&#xff1a;2. INSERT INTO ... ON DUPLICATE KEY UPDATE 语句结合事务&#xff1a;3. INSERT INTO ... SELECT ... FROM ... ON DUPLICATE KEY UPDATE 语句&#xff1a;4. 根…

Kyligence一站式数字化建设的新指标

1.数字化时代中小企业的痛点与难点 数字化时代&#xff0c;众多领先企业纷纷利用数字化技术&#xff0c;实现业务精细化运营和降本增效&#xff0c;从而有效提升企业的盈利水平和竞争力。其中最重要的手段就是业务过程数据指标化&#xff0c;通过对指标的定义、监控、分析和洞…

【容器化应用程序设计和开发】2.5 容器化应用程序的安全性和合规性考虑

往期回顾&#xff1a; 第一章&#xff1a;【云原生概念和技术】 第二章&#xff1a;2.1 容器化基础知识和Docker容器 第二章&#xff1a;2.2 Dockerfile 的编写和最佳实践 第二章&#xff1a;2.3 容器编排和Kubernetes调度 第二章&#xff1a;2.4 容器网络和存储 2.5 容器…

python 3.9 安装wordcloud

1. pip install wordcloud 安装不成功&#xff0c;或者安装成功&#xff0c;python ide中不能用。 2. 去网上单独下载适合3.9 的安装包 &#xff0c;网址&#xff1a;https://www.lfd.uci.edu/~gohlke/pythonlibs/#wordcloud 3.选择版本 wordcloud-1.8.1-cp39-cp39-win_amd64…

企业布局新媒体矩阵,如何走得更远?

企业搭建新媒体矩阵有很多好处——扩大品牌声量、丰富内容形式、提高宣传效率、降低运营风险、节省广告成本...... 即便如此&#xff0c;能真正让新媒体矩阵产生如此效果的企业&#xff0c;却是凤毛麟角。 更多的企业&#xff0c;往往冒然入场&#xff0c;也黯然离场&#xff0…

html实现经典坦克大战小游戏

文章目录 1.设计来源1.1 游戏主界面1.2 游戏界面 2.效果和源码2.1 动态效果2.2 源代码 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/130617759 html实现经典坦克大战小游戏 ,这是一款很老的游戏&#xff0c;…

矩阵计算(求导)

亚导数 当函数不可微时&#xff0c;不可计算出其普通的导数&#xff0c;此时便需要引入亚导数Example: 函数 y ∣ x ∣ y|x| y∣x∣ 不可微&#xff0c;其亚导数为 ∂ ∣ x ∣ ∂ x { 1 , x > 0 − 1 , x < 0 a , x 0 , a ∈ [ 0 , 1 ] \frac{\partial |x|}{\parti…

KDZD绝缘子表面电导盐密度测试仪

一、简介 智能电导盐密测试仪&#xff0c;也称为直读式等值盐密度测试仪&#xff0c;专为测试智能电导盐密度而设计。系统内置智能电导盐密度计算公式&#xff0c;读数直观。 人机交互采用真彩TFT液晶屏&#xff0c;操作简单&#xff0c;测试参数和结果一目了然。仪器自带微型打…

怎么开发外贸网站

随着全球经济的发展&#xff0c;越来越多的企业选择走上国际化的道路&#xff0c;开展国际贸易业务。而外贸网站是一个相对常见的开展国际贸易业务的平台。那么&#xff0c;如何开发一款优秀的外贸网站呢&#xff1f; 首先&#xff0c;我们需要明确外贸网站的目标用户群体。由…

21. Unity - 2D游戏开发小计04 --- 2D摄像机跟随移动、精灵素材替换粒子特效、射线投射、音频特效播放、音频空间感效果

1. 摄像机跟随(Cinemachine插件)和视野边界设置 在2D游戏中如果想让摄像机跟随场景中的玩家角色进行移动,最简单的实现方式是可以使用一个插件 – Cinemachine进行设置: 首先在菜单栏中打开Package Manager资源管理窗口,然后选择Unity Registry,在右侧的搜索栏搜索下载安…

严格平稳、弱平稳、白噪声与渐进独立

严格平稳、弱平稳、白噪声与渐进独立 文章目录 严格平稳、弱平稳、白噪声与渐进独立[toc]1 严格平稳2 弱平稳3 白噪声4 渐进独立 1 严格平稳 严格平稳&#xff1a;给定随机过程 { x t } t 1 ∞ \{x_t\}_{t1}^\infty {xt​}t1∞​&#xff0c;对于任意 m m m个时期的集合 { t …