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

目录:

  • 前言
  • 进程间通信的目的
  • 进程间通信的方式
  • 管道
    • 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. 共享内存映射在进程地址空间的共享区


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

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

相关文章

MyBatis学习 (一) 配置文件解析流程

MyBatis源码学习 最近在学习MyBatis的代码。记录下 首先下载下源码&#xff1a; https://github.com/mybatis/parent https://github.com/mybatis/mybatis-3 parent为父控依赖。也需要下载。 入口 InputStream inputStream null; try {// 获取配置文件inputStream Reso…

为AIGC敲响警钟!千亿级赛道为何成了作恶温床?

‍数据智能产业创新服务媒体 ——聚焦数智 改变商业 随着人工智能通用大模型的问世&#xff0c;全球对AIGC技术的强大潜力有了更加深刻的认识。然而&#xff0c;这也引发了诸多关于AIGC技术可信度、隐私保护以及知识产权等问题的争议&#xff0c;引起了广泛关注。 5月9日&…

开源单用户客服系统源码-上传附件功能-elementui 异步上传文件【唯一客服开发商】...

之前开源的单用户客服系统&#xff0c;上传附件成功后&#xff0c;还不能展示出文件形式&#xff0c;今天把上传展示出文件形式给开发完善一下。 我想要实现的效果是&#xff0c;展示出文件的名称和大小信息 后端返回一个带有文件信息的json结果&#xff0c;前端把该信息组织一…

ubuntu系统配置大恒相机驱动并读取ros话题

文章目录 0. 说明1. 安装大恒相机sdk1.1 下载1.2 安装sdk(用于配置ip和调试相机参数)(1) 电脑网卡配置(网卡固定ip)(2)查看相机图像以及配置相机参数 2. 安装ros驱动包(注&#xff1a;大恒相机官方没ros驱动)2.0 正确流程2.1 错误示范2.1 报错1--缺包2.2 报错2--包编译顺序问题…

arduino 导入 Brain 库

一、引言 最近在做一个可以用脑电波控制的arduino小车&#xff0c;需要用到Brain这个库&#xff0c;而且需要自己导入才能使用。之前试了很多方法&#xff0c;导入成功了&#xff0c;过了几个月又忘记怎么导入了&#xff0c;今天想起来记录一下&#xff0c;好记性不如烂笔头。 …

Java集合类

目录 一、整体架构图 二、List集合类(有序的&#xff0c;可重复的) 1.顺序列表ArrayList 2.链式列表LinkedList 三、Set集合类(不可重复) 1.HashSet(哈希集合) 2.LinkedHashSet(链式哈希集合) 3.TreeSet(树形集合) 四、Map集合类(无序&#xff0c;键唯一&#xff0c;值…

MySQL实战之主从数据同步机制

主从同步的重要性&#xff1a; 解决数据可靠性的问题需要用到主从同步&#xff1b;解决 MySQL 服务高可用要用到主从同步&#xff1b;应对高并发的时候&#xff0c;还是要用到主从同步。 一、MySQL 主从同步流程 当客户端提交一个事务到 MySQL 的集群&#xff0c;直到客户端收…

跨域时怎么处理 cookie?

前言 一个请求从发出到返回&#xff0c;需要浏览器和服务端的协调配合。浏览器要把自己的请求参数带给服务端&#xff0c;服务端校验参数之后&#xff0c;除了返回数据&#xff0c;也可能会顺便把请求是否缓存&#xff0c;cookie等信息告诉浏览器。当请求是跨域请求的时候&…

项目调研 | Loopring研究报告

一、项目简介及愿景 Loopring协议是一个专为应用程序开发的 zkRollup 协议、一个中继器、一个 L2 非托管交易所、一个智能钱包。用户可以在其中使用、交易和存储资产&#xff0c;同时让资产获得增长。 上述Loopring这些Title具体详情如下&#xff1a; 作为协议&#xff0c;Loop…

[Golang] 设计模式以及单例设计模式实例实现

&#x1f61a;一个不甘平凡的普通人&#xff0c;致力于为Golang社区和算法学习做出贡献&#xff0c;期待您的关注和认可&#xff0c;陪您一起学习打卡&#xff01;&#xff01;&#xff01;&#x1f618;&#x1f618;&#x1f618; &#x1f917;专栏&#xff1a;算法学习 &am…

金3银四结束了,回顾一下我2个月面试的公司....

金三银四结束了&#xff0c;还没有 offer 的同学不要气馁&#xff0c;该来的迟早会来。楼主从 年底 月有想法跳槽开始准备春招&#xff0c;一开始也是惨不忍睹&#xff0c;后来慢慢进入状态最近的面试基本都能走到终面&#xff0c;所以好好坚持&#xff0c;最后一定会有好结果的…

Pandas + ChatGPT 超强组合,pandas-ai :交互式数据分析和处理新方法

Python Pandas是一个为Python编程提供数据操作和分析功能的开源工具包。这个库已经成为数据科学家和分析师的必备工具。它提供了一种有效的方法来管理结构化数据(Series和DataFrame)。 在人工智能领域&#xff0c;Pandas经常用于机器学习和深度学习过程的预处理步骤。Pandas通过…

基于主从博弈的综合能源服务商动态定价策略研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

在滴滴和字节跳动划水4年,过于真实了...

先简单交代一下吧&#xff0c;沅哥是某不知名211的本硕&#xff0c;18年毕业加入滴滴&#xff0c;之后跳槽到了头条&#xff0c;一直从事测试开发相关的工作。之前没有实习经历&#xff0c;算是四年半的工作经验吧。 这四年半之间他完成了一次晋升&#xff0c;换了一家公司&am…

如何利用python实现灰色关联分析?

1.灰色关联分析简介 灰色系统这个概念是相对于白色系统和黑色系统而言的。从控制论的知识里&#xff0c;颜色一般代表对于一个系统我们已知信息的多少&#xff0c;白色代表信息量充足&#xff0c;黑色代表我们其中的构造并不清楚的系统&#xff0c;而灰色介于两者之间&#xf…

LabVIEWCompactRIO 开发指南18 使用网络流发送消息和命令

LabVIEWCompactRIO 开发指南18 使用网络流发送消息和命令 默认情况下&#xff0c;网络流旨在最大化吞吐量&#xff0c;但可以轻松实现它们以最大化发送命令或消息的低延迟。 为本部分提供LabVIEW示例代码 命令发送器体系结构 命令发送器是CompactRIO控制器必须响应的任何命…

pga_aggregate_limit和process关系

之前部署19c时&#xff0c;配置pga_aggregate_limit都是直接配置成0了&#xff0c;配置processes的大小也比较随意&#xff0c;上周维护一个客户安装的环境&#xff0c;重启数据库数据库时告警了&#xff0c;才第一次认真对面了 SYSorcl1> startup ; ORA-00093: pga_aggreg…

无代码时代来了,程序员会失业吗?不,程序员又不够用了!

有人问我无代码时代来了&#xff0c;程序员会失业吗&#xff1f;太难了&#xff0c;秃了头就算了&#xff0c;连工作也保不住了&#xff1f; 先说观点&#xff1a;并不会 因为&#xff0c;无代码不是真正意义上的无代码。 无代码开发的使用对象是编程小白&#xff08;我猿是…

Linux篇1

Linux 1. 概述1.1 内容概要1.2 Linux发展1.3 Linux对比Windows 2. 虚拟机下安装CentOS系统2.1 下载安装VMware2.1.1 官网下载VMware软件2.1.2 安装VMware 2.2 下载CentOS镜像2.3 创建虚拟机&#xff08;在虚拟机中安装CentOS&#xff09;2.3.1 创建虚拟硬件环境2.3.2 安装CentO…

字典核心底层原理

字典对象的核心是散列表。散列表是一个稀疏数组&#xff08;总是有空白元素的数组&#xff09;&#xff0c;数组的每个单元叫做bucket。每个bucket有两部分&#xff1a;一个是键对象的引用&#xff0c;一个是值对象的引用。 由于&#xff0c;所有bucket结构和大小一致&#xf…