【Linux】进程间通信概念 | 匿名管道

文章目录

  • 一、什么是进程间通信
      • 进程间通信的概念
      • 进程间通信的目的
      • 进程间通信的分类
      • 进程间通信的本质
  • 二、什么是管道
  • 三、匿名管道
      • 匿名管道的原理
          • ✨站在内核角度理解管道
          • ✨站在文件描述符角度理解管道
      • pipe系统调用
      • fork后在父子进程间使用管道通信
          • 代码实现
      • 匿名管道的读写规则
      • 管道的5种特性
          • 1. 匿名管道的局限性
          • 2. 管道内部自带同步与互斥机制
          • 3. 管道的生命周期随进程:
          • 4. 管道提供的是面向字节流的流式服务:
          • 5. 管道是单向通信的,半双工通信的一种特殊情况:
  • 四、运用匿名管道建立进程池

[!Abstract] 进程间通信重点

  • 进程间通信介绍
  • 管道
  • 消息队列
  • 共享内存
  • 信号量

一、什么是进程间通信

进程间通信的概念

进程间通信简称IPC(Interprocess communication),是操作系统中的一个重要概念,它允许不同的进程在执行过程中交换数据、共享资源、协调行为等。在多道程序设计环境下,多个进程可能需要相互通信以完成复杂的任务,而进程间通信提供了各种机制来实现这种交互

进程间通信的目的

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

进程间通信的分类

  1. 管道:管道是最早的进程间通信机制之一,最早出现在UNIX系统中。它是一种简单而有效的通信方式,适用于具有父子关系的进程。管道只能用于具有共同祖先的进程之间的通信,通常用于父进程与子进程之间。管道分为:

    • 匿名管道pipe
    • 命名管道
  2. System V IPC:是一套在UNIX系统中引入的标准,包括:

    • System V 消息队列
    • System V 共享内存
    • System V 信号量
      System V IPC 提供了更为灵活和通用的进程间通信机制,使得不同进程之间能够更灵活地交换信息和共享资源。
  3. POSIX IPC:是为UNIX-like系统定义的一套标准。POSIX 进程间通信机制是在System V IPC的基础上进行改进和扩展的,以提供更简单和一致的接口。POSIX IPC包括:

    • 消息队列
    • 共享内存
    • 信号量
    • 互斥量
    • 条件变量
    • 读写锁

进程间通信的本质

进程通信的本质是,让不同的进程看到同一份资源
这种资源通常由操作系统提供。


二、什么是管道

管道是Unix中最古老的进程间通信的形式。
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”

例如,统计我们当前使用云服务器上的登录用户个数。可以在bash下输入命令 who | wc -l 执行一条简单的管道操作,这条命令的作用是将两个命令连接起来,将第一个命令的输出作为第二个命令的输入。
请添加图片描述

  1. who:这个命令通常用于显示当前登录系统的用户信息,包括用户名、登录时间等。执行 who 会输出一些用户信息的列表。

  2. |:这是管道符号,它将第一个命令的输出传递给第二个命令的输入。在这个例子中,它将 who 命令的输出传递给下一个命令。

  3. wc -lwc 是用于统计文件中行数、字数和字符数的命令,而 -l 参数表示只统计行数。因此,wc -l 会对输入的文本进行行数统计。

请添加图片描述

三、匿名管道

匿名管道的原理

匿名管道用于进程间通信,且仅限于本地父子进程之间的通信。
使用匿名管道实现父子进程间通信的原理就是,让两个父子进程先看到同一份被打开(内存中)的文件资源,然后父子进程就可以对该文件进行写入或是读取操作,进而实现父子进程间通信。

✨站在内核角度理解管道

看待管道,就如同看待文件一样,管道的使用和文件一致,迎合了“Linux一切皆文件思想”。

请添加图片描述

[!Attention] 注意:

  1. 为什么父进程对匿名管道文件进行写操作的时候,不会发生写时拷贝?
    匿名管道的数据传递是通过内核缓冲区进行的,而不是直接访问用户空间的内存。当父进程写入数据时,数据首先被复制到内核缓冲区,然后再由内核传递给子进程。这种传递方式不涉及用户空间的共享,因此不会引发写时拷贝。记住,写时拷贝发生在用户空间!

  1. 管道用的是文件的方案,那操作系统为什么不把进程进行通信的数据刷新到磁盘当中?
    因为这样做有IO参与会降低效率,而且也没有必要。也就是说,这种文件是一批不会把数据写到磁盘当中的文件,换句话说,磁盘文件和内存文件不一定是一一对应的,有些文件只会在内存当中存在,而不会在磁盘当中存在。
✨站在文件描述符角度理解管道

请添加图片描述

pipe系统调用

pipe函数用于创建匿名管道,pipe函数的函数原型和需要包含的头文件如下:

   #include <unistd.h>int pipe(int pipefd[2]);
   #include <fcntl.h>              /* Obtain O_* constant definitions */#include <unistd.h>int pipe2(int pipefd[2], int flags);

pipe函数的参数是一个输出型参数,数组pipefd用于返回两个指向管道读端和写端的文件描述符:

数组元素含义
pipefd[0]表示管道的读端
pipefd[1]表示管道的写端

返回值:pipe函数调用成功时返回0,调用失败时返回-1,并设置errno来指示错误类型。

fork后在父子进程间使用管道通信

在创建匿名管道实现父子进程间通信的过程中,需要pipe函数和fork函数搭配使用,具体步骤如下:请添加图片描述

代码实现

例子:从键盘读取数据,子进程写入管道,父进程读取管道,写到屏幕

#include <iostream>
#include <cassert>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define MAX 1024using namespace std;int main()
{// 第1步,建立管道int pipefd[2] = {0};int n = pipe(pipefd);assert(n == 0); // 意料之中,用assert,意料之外,用if(void)n; // 防止编译器告警cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << endl;// 第2步,创建子进程pid_t id = fork();if (id < 0){perror("fork");return 1;}// 子写,父读// 第3步,父子关闭不需要的fd,形成单向通信的管道if (id == 0){// 子进程 - 关闭读端close(pipefd[0]);// w - 只向管道写入,没有打印int cnt = 0;while(true){char message[MAX];snprintf(message, sizeof(message), "hello father, I am child, pid: %d, cnt: %d", getpid(), cnt);cnt++;write(pipefd[1], message, strlen(message));sleep(1);if(cnt > 10) break;}cout << "child close w piont, quit" << endl;close(pipefd[1]);exit(0);}// 父进程 - 关闭写端close(pipefd[1]);char buffer[MAX];while(true){ssize_t n = read(pipefd[0], buffer, sizeof(buffer)-1);if(n > 0){buffer[n] = '\0'; // '\0', 当做字符串尾cout << getpid() << ", " << "child say: " << buffer << " to me!" << endl;}else if(n == 0){cout << "child quit, me too !" << endl;break;}}cout << "father read point close"<< endl;close(pipefd[0]);sleep(5);int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid == id){cout << "wait success, child exit sig: " << (status&0x7F) << endl;}return 0;
}

运行结果:
请添加图片描述

匿名管道的读写规则

  1. 当没有数据可读时

    • O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
    • O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
  2. 当管道满的时

    • O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
    • O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
  3. 如果所有管道写端对应的文件描述符被关闭,则read返回0

  4. 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致正在write的进程退出。

[!Test] 验证一下:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{int fd[2] = { 0 };if (pipe(fd) < 0){ //使用pipe创建匿名管道perror("pipe");return 1;}pid_t id = fork(); //使用fork创建子进程//子写,父读if (id == 0){//childclose(fd[0]); //子进程关闭读端//子进程向管道写入数据const char* msg = "hello father, I am child...";int count = 10;while (count--){write(fd[1], msg, strlen(msg));sleep(1);}close(fd[1]); //子进程写入完毕,关闭文件exit(0);}//fatherclose(fd[1]); //父进程关闭写端close(fd[0]); //父进程直接关闭读端(导致子进程被操作系统杀掉)int status = 0;waitpid(id, &status, 0);printf("child exit signal:%d\n", status & 0x7F); //打印子进程收到的信号return 0;
}

子进程没有正常向管道内写入,而是直接退出,退出信号是13,通过kill -l命令可以查看13对应的具体信号。
请添加图片描述

操作系统向子进程发送的是SIGPIPE信号将子进程终止。

  1. 原子性:
    • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
    • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

[!Abstract] 关于原子性


在Linux中,当写入的数据量不超过PIPE_BUF时,内核会尽力保证写入的原子性。原子性是指一个操作在执行的过程中不会被中断,要么全部执行成功,要么全部不执行,不存在部分执行的情况。

PIPE_BUF 是一个常量,表示管道缓冲区的原子大小,其值是系统相关的,通常是4096字节。当要写入的数据量小于等于 PIPE_BUF 时,写入操作将被视为原子操作。这意味着,如果有多个进程尝试同时写入不超过 PIPE_BUF 大小的数据到同一个管道,操作系统会保证这些数据不会相互交叉,即写入的数据是完整的。

然而,当要写入的数据量大于 PIPE_BUF 时,Linux不再保证写入的原子性。这是因为在写入大量数据时,内核可能需要多次切换上下文,而这期间其他进程也可能进行写入操作,导致写入的数据不再是原子的。这并不意味着数据写入一定会出现截断或混淆,但是操作系统不再保证原子性。

原子性在并发编程中是一个重要的概念,它确保多个线程或进程在访问共享资源时不会导致数据不一致或损坏。因此,了解在特定情况下操作的原子性是确保并发程序正确执行的重要一步。


管道的5种特性

1. 匿名管道的局限性

匿名管道,可以允许具有血缘关系的进程之间进行进程间通信,常用于父子,也可以用于兄弟爷孙,匿名管道的场景仅限于此

2. 管道内部自带同步与互斥机制

我们将一次只允许一个进程使用的资源,称为临界资源。管道在同一时刻只允许一个进程对其进行写入或是读取操作,因此管道也就是一种临界资源。

临界资源是需要被保护的,若是我们不对管道这种临界资源进行任何保护机制,那么就可能出现同一时刻有多个进程对同一管道进行操作的情况,进而导致同时读写、交叉读写以及读取到的数据不一致等问题。

为了避免这些问题,内核会对管道操作进行同步与互斥:

  • 同步: 两个或两个以上的进程在运行过程中协同步调,按预定的先后次序运行。比如,A任务的运行依赖于B任务产生的数据。
  • 互斥: 一个公共资源同一时刻只能被一个进程使用,多个进程不能同时使用公共资源。

实际上,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。对于管道的场景来说,互斥就是两个进程不可以同时对管道进行操作,它们会相互排斥,必须等一个进程操作完毕,另一个才能操作,而同步也是指这两个不能同时对管道进行操作,但这两个进程必须要按照某种次序来对管道进行操作。

也就是说,互斥具有唯一性和排它性,但互斥并不限制任务的运行顺序,而同步的任务之间则有明确的顺序关系。

3. 管道的生命周期随进程:

管道(通常指的是匿名管道)的生命周期是与创建它的进程相关联的。当一个进程创建了一个管道后,这个管道会一直存在。当进程退出时,操作系统会自动关闭所有打开的文件描述符,包括管道相关的文件描述符。关闭文件描述符会触发相应的资源释放操作,例如,管道中的缓冲区、文件表项等。

4. 管道提供的是面向字节流的流式服务:

对于进程A写入管道当中的数据,进程B每次从管道读取的数据的多少是任意的,这种被称为流式服务,与之相对应的是数据报服务:

  • 流式服务: 数据没有明确的分割,不分一定的报文段。
  • 数据报服务: 数据有明确的分割,拿数据按报文段拿。

管道提供的是面向字节流的服务,也就是说,它将数据视为一系列的字节,而不考虑字节之间的结构。这与面向消息的通信机制(如消息队列)不同,消息队列更注重消息的边界和结构。

在面向字节流的管道中,数据是连续的流,没有明确的消息边界。这种特性使得管道适用于一些场景,例如通过管道传递文本或二进制数据。但需要注意的是,由于没有消息边界的概念,接收端可能需要额外的协议或标记来解释和处理数据。

5. 管道是单向通信的,半双工通信的一种特殊情况:

在数据通信中,数据在线路上的传送方式可以分为以下三种:

  1. 单工通信(Simplex Communication):单工模式的数据传输是单向的。通信双方中,一方固定为发送端,另一方固定为接收端。
  2. 半双工通信(Half Duplex):半双工数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。
  3. 全双工通信(Full Duplex):全双工通信允许数据在两个方向上同时传输,它的能力相当于两个单工通信方式的结合。全双工可以同时(瞬时)进行信号的双向传输。

管道是一种单向通信机制,通常是半双工(Half-Duplex)的。半双工通信意味着数据在一个方向上传输,而在另一个方向上传输时需要另外的管道。在典型的匿名管道中,一个进程负责写入数据,而另一个进程负责读取数据。要实现双向通信,需要创建两个独立的管道,或者考虑其他的通信机制(如全双工通信的命名管道或套接字)。

单向通信和半双工通信的特性使得管道更适合一些特定的应用场景,如父子进程之间的通信,或者通过管道将输出从一个进程传递到另一个进程。

[!Improtant] 重新理解命令行中的管道“|”,和pipe系统调用:


Bash中的 | 管道以及通过pipe系统调用创建的管道都是匿名管道
事实上,Bash中的 | 管道符底层调用了一些系统调用,其中就包括 pipe 系统调用。在Linux系统中,pipe 系统调用用于创建管道,而fork 系统调用用于创建子进程。通过这两个系统调用的组合,Bash能够实现进程间通信。具体步骤如下:

  1. Bash 使用 pipe 系统调用创建一个管道,得到两个文件描述符,一个用于管道的写入端,一个用于读取端。
  2. Bash 使用 fork 系统调用创建一个子进程。这个子进程将成为 | 符号左侧命令的进程。
  3. 在父进程(Bash)中,将标准输出(文件描述符1)重定向到管道的写入端。
  4. 在子进程中,将标准输入(文件描述符0)重定向到管道的读取端。
  5. Bash 分别执行 | 符号两侧的命令,它们分别成为父进程和子进程的执行体。

这样,左侧命令的输出就通过管道传递给了右侧命令,实现了进程间通信。所以,Bash中的 | 管道符在底层使用了 pipefork 系统调用来创建管道和子进程。


四、运用匿名管道建立进程池

ProcessPool.cc:

#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"const int num = 5;
static int number = 1;class channel
{
public:channel(int fd, pid_t id) : ctrlfd(fd), workerid(id){name = "channel-" + std::to_string(number++);}public:int ctrlfd;pid_t workerid;std::string name;
};void Work()
{while (true){int code = 0;ssize_t n = read(0, &code, sizeof(code));if (n == sizeof(code)){if (!init.CheckSafe(code))continue;init.RunTask(code);}else if (n == 0){break;}else{// do nothing}}std::cout << "child quit" << std::endl;
}void PrintFd(const std::vector<int>& fds)
{std::cout << getpid() << " close fds: ";for (auto fd : fds){std::cout << fd << " ";}std::cout << std::endl;
}// 传参形式:
// 1. 输入参数:const &
// 2. 输出参数:*
// 3. 输入输出参数:&
void CreateChannels(std::vector<channel>* c)
{// bugstd::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();assert(id != -1);// 3. 构建单向通信信道if (id == 0) // child{if (!old.empty()){for (auto fd : old){close(fd);}PrintFd(old);}close(pipefd[1]);dup2(pipefd[0], 0);Work();exit(0); // 会自动关闭自己打开的所有的fd}// fatherclose(pipefd[0]);c->push_back(channel(pipefd[1], id));old.push_back(pipefd[1]);// childid, pipefd[1]}
}void PrintDebug(const std::vector<channel>& c)
{for (const auto& channel : c){std::cout << channel.name << ", " << channel.ctrlfd << ", " << channel.workerid << std::endl;}
}void SendCommand(const std::vector<channel>& c, bool flag, int num = -1)
{int pos = 0;while (true){// 1. 选择任务int command = init.SelectTask();// 2. 选择信道(进程)const auto& channel = c[pos++];pos %= c.size();// debugstd::cout << "send command " << init.ToDesc(command) << "[" << command << "]"<< " in "<< channel.name << " worker is : " << channel.workerid << std::endl;// 3. 发送任务write(channel.ctrlfd, &command, sizeof(command));// 4. 判断是否要退出if (!flag){num--;if (num <= 0)break;}sleep(1);}std::cout << "SendCommand done..." << std::endl;
}
void ReleaseChannels(std::vector<channel> c)
{// version 2// int num = c.size() - 1;// for (; num >= 0; num--)// {//     close(c[num].ctrlfd);//     waitpid(c[num].workerid, nullptr, 0);// }// version 1for (const auto& channel : c){close(channel.ctrlfd);waitpid(channel.workerid, nullptr, 0);}// for (const auto &channel : c)// {//     pid_t rid = waitpid(channel.workerid, nullptr, 0);//     if (rid == channel.workerid)//     {//         std::cout << "wait child: " << channel.workerid << " success" << std::endl;//     }// }
}
int main()
{std::vector<channel> channels;// 1. 创建信道,创建进程CreateChannels(&channels);// 2. 开始发送任务const bool g_always_loop = true;// SendCommand(channels, g_always_loop);SendCommand(channels, !g_always_loop, 10);// 3. 回收资源,想让子进程退出,并且释放管道,只要关闭写端ReleaseChannels(channels);return 0;
}

Task.hpp:

#pragma once#include <iostream>
#include <functional>
#include <vector>
#include <ctime>
#include <unistd.h>// using task_t = std::function<void()>;
typedef std::function<void()> task_t;void Download()
{std::cout << "我是一个下载任务"<< " 处理者: " << getpid() << std::endl;
}void PrintLog()
{std::cout << "我是一个打印日志的任务"<< " 处理者: " << getpid() << std::endl;
}void PushVideoStream()
{std::cout << "这是一个推送视频流的任务"<< " 处理者: " << getpid() << std::endl;
}// void ProcessExit()
// {
//     exit(0);
// }class Init
{
public:// 任务码const static int g_download_code = 0;const static int g_printlog_code = 1;const static int g_push_videostream_code = 2;// 任务集合std::vector<task_t> tasks;public:Init(){tasks.push_back(Download);tasks.push_back(PrintLog);tasks.push_back(PushVideoStream);srand(time(nullptr) ^ getpid());}bool CheckSafe(int code){if (code >= 0 && code < tasks.size())return true;elsereturn false;}void RunTask(int code){return tasks[code]();}int SelectTask(){return rand() % tasks.size();}std::string ToDesc(int code){switch (code){case g_download_code:return "Download";case g_printlog_code:return "PrintLog";case g_push_videostream_code:return "PushVideoStream";default:return "Unknow";}}
};Init init; // 定义对象

Makefile:

proc:ProcessPool.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f proc

运行:
请添加图片描述


如果涉及到在文件系统中创建一个有名的管道,那么就是在使用命名管道,下一篇文章我们讲命名管道的概念。

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

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

相关文章

stable diffusion学习笔记——文生图(一)

模型设置 基本模型 基本模型也就是常说的checkpoint&#xff08;大模型&#xff09;&#xff0c;基本模型决定了生成图片的主体风格。 如上图所示&#xff0c;基本模型的后缀为.safetensors。需要存放在特定的文件夹下。 如果用的是启动器&#xff0c;可以在启动器内直接下载。…

GPT-SoVITS 本地搭建踩坑

GPT-SoVITS 本地搭建踩坑 前言搭建下载解压VSCode打开安装依赖包修改内容1.重新安装版本2.修改文件内容 运行总结 前言 传言GPT-SoVITS作为当前与BertVits2.3并列的TTS大模型&#xff0c;于是本地搭了一个&#xff0c;简单说一下坑。 搭建 下载 到GitHub点击此处下载 http…

在JavaScript中获取当前时间yyyymmddhhmmss的六种实现方式

在编写JavaScript代码时&#xff0c;遇到需要获取当前日期和时间并将其格式化为 yyyymmddhhmmss 字符串的情况。可以使用本文中介绍的几种实现方式中的任意一种。 方法一&#xff1a;使用Date对象 使用 Date 对象来获取当前日期和时间。 示例代码&#xff1a; const now new D…

NC开发客户端(前端)连接启动失败can‘t connect to server, please wait

效果图 解决方法 IP地址和端口要对应 1-IP地址中间启动&#xff0c;肯定是这个127.0.0.1 2-端口号&#xff0c;要对应中间件启动在控制台输出的端口 或者是在home目录-》bin-》sysConfig.bat这里面的服务器&#xff0c; 里面可以看到对应启动ip地址和端口

Qt使用中文字符串乱码的问题

文章目录 vs编译器下第一种解决方式第二种解决方式 Qt编译器下 我们在使用qt的时候有时候会遇到打印中文字符串的时候出现中文乱码的问题&#xff0c;主要是由于Qt的QString字符串存储的方式是使用utf-8的编码方式&#xff0c;如果我们本地的文件是使用GBK方式的编码再使用中文…

React一学就会(3): 强化练习一

前言 兄弟们点个关注点点赞&#xff0c;有什么建议在评论里留言说一下&#xff0c;一定要和我多多互动啊&#xff0c;这样我才有动力创作出更有品质的文章。 这节课我们用前两节课的知识做一个实践&#xff0c;在实战中巩固我们所学。本来我想借用官方的示例翻译一下&#xf…

数据库操作

数据库操作 1、 表之间连接 MYSQL 题 1、取第二高薪2、取第N高薪3、分数排名 inner join&#xff1a;2表值都存在 outer join&#xff1a;附表中值可能存在null的情况。 总结&#xff1a; ①A inner join B&#xff1a;取交集 ②A left join B&#xff1a;取A全部&#…

Vue深入学习4—指令和生命周期

1.Vue是怎么识别 v- 指令的&#xff1f; 首先将HTML结构解析成属性列表&#xff0c;存入到数组中&#xff0c;接着遍历数组中的每一个节点&#xff0c;获取到不同指令对应的方法。 // 将HTML看作真正的属性列表 var ndoeAttrs node.attributes; var self this; // 类数组对象…

原创改进 | 融合蝠鲼觅食与联想学习的量子多目标灰狼优化算法(Matlab)

​前面的文章里作者介绍了多目标灰狼优化算法(Multi-Objective Grey Wolf Optimizer&#xff0c;MOGWO)&#xff0c;该算法是由Mirjalili等(灰狼算法的提出者)于2016年提出[1]&#xff0c;发表在中科院一区期刊《expert systems with applications》。 MOGWO保留了灰狼算法的种…

Leetcode 第 111 场双周赛题解

Leetcode 第 111 场双周赛题解 Leetcode 第 111 场双周赛题解题目1&#xff1a;2824. 统计和小于目标的下标对数目思路代码复杂度分析 题目2&#xff1a;2825. 循环增长使字符串子序列等于另一个字符串思路代码复杂度分析 题目3&#xff1a;2826. 将三个组排序思路代码复杂度分…

PCL Kdtree 使用示例

PCL Kdtree 使用示例 文章目录 PCL Kdtree 使用示例一、关于 KDTree二、关于最近邻搜索三、复杂度分析四、C代码示例五、关键函数说明nearestKSearch 函数说明 一、关于 KDTree 点云数据主要是&#xff0c; 表征 目标表面 的海量点集合&#xff0c; 并不具备传统实体网格数据的…

P8651 [蓝桥杯 2017 省 B] 日期问题

#include <iostream> #include <string> using namespace std;int first; int second; int third; int day[13]{0,31,0,31,30,31,30,31,31,30,31,30,31};//每月日期bool select (int i,int j,int k){if ((i%100 first) && (j second) && (k thi…

分段函数线性化方法matlab测试

目录 1 使用0-1变量将分段函数转换为线性约束 2 连续函数采用分段线性化示例 3 matlab程序测试 4 matlab测试结果说明 5 分段线性化应用 1 使用0-1变量将分段函数转换为线性约束 2 连续函数采用分段线性化示例 3 matlab程序测试 clc;clear all; gn10;tn1; x_pfsdpvar(1, t…

【ArcGIS遇上Python】python实现批量XY坐标生成shp点数据文件

单个手动生成:【ArcGIS风暴】ArcGIS 10.2导入Excel数据X、Y坐标(经纬度、平面坐标),生成Shapefile点数据图层 文章目录 一、问题分析二、解决办法三、注意事项一、问题分析 现有多个excel、txt或者csv格式的坐标数据,需要根据其坐标批量一键生成shp点数据,如下X为经度,…

Java - OpenSSL与国密OpenSSL

文章目录 一、定义 OpenSSL&#xff1a;OpenSSL是一个开放源代码的SSL/TLS协议实现&#xff0c;也是一个功能丰富的加密库&#xff0c;提供了各种主要的加密算法、常用的密钥和证书封装管理功能以及SSL协议。它被广泛应用于Web服务器、电子邮件服务器、VPN等网络应用中&#x…

高考复习技巧考研资料、美赛论文及代码,数据收集网站(初高中招生考试全科试卷等)

图&#xff0c;就要从“点、线、面的位置关系”这一内核开始发散&#xff0c;第一层级为彼此的位置关系&#xff0c;平行、相交、异面&#xff08;两直线间位置&#xff09;、垂直&#xff08;相交或异面中的特殊位置&#xff09;&#xff0c;多面体、旋转体等&#xff0c;然后…

前端实现弹小球功能

这篇文章将会做弹小球游戏&#xff0c;弹小球游戏大家小时候都玩过&#xff0c;玩家需要在小球到达游戏区域底部时候控制砖块去承接小球&#xff0c;并不断的将小球弹出去。 首先看一下实现的效果。 效果演示 玩家需要通过控制鼠标来实现砖块的移动&#xff0c;保证在小球下落…

Linux 文件和文件夹的创建与删除

目录 一. 新建1.1 mkdir 新建文件夹1.2 touch 新建空文件1.3 vi命令创建文件1.4 > 和 >> 新建文件 二. 删除 一. 新建 1.1 mkdir 新建文件夹 -p&#xff1a;递归的创建文件夹&#xff0c;当父目录不存在的时候&#xff0c;会自动创建 mkdir -p test1/test2/test31.…

递归神经网络:(01/4) 顺序数据处理的骨干

koushikkushal95 一、说明 循环神经网络是一个里程碑式的模型框架&#xff0c;它是对时间串处理的最基本构架&#xff1b;而理解RNN也是对自然语言处理模型的开端&#xff0c;本篇将对该模型的基本原理进行介绍。 二、顺序数据处理的架构 递归神经网络 &#xff08;RNN&#xf…

字典树-Python

字典树 字典树又叫前缀树、单词查找树&#xff0c;树形结构&#xff0c;是哈希树的变种。能够统计、排序和保存大量的字符串&#xff0c;经常被搜索引擎系统用于文本词频统计。优点是利用字符串的公共前缀来减少查询时间&#xff0c;最大程度减少无谓字符串的比较&#xff0c;…