【Linux】进程间通信——管道

目录

写在前面的话

什么是进程间通信

为什么要进行进程间通信

进程间通信的本质理解

进程间通信的方式

管道

System V IPC

POSIX IPC

管道

什么是管道

 匿名管道

什么是匿名管道

匿名管道通信的原理

pipe()的使用

匿名管道通信的特点

拓展代码

 命名管道

什么是命名管道

命名管道通信的原理

mkfifo的使用

代码模拟命名管道通信过程


写在前面的话

        本章是首次提出进程间通信的概念,所以会先介绍进程间通信的相关概念,以及整体的框架结构。

        而本文重点是先介绍进程间通信的基本概念,然后重点介绍进程间通信的第一种方式:管道。

什么是进程间通信

        进程间通信(Inter-Process Communication,IPC)是指操作系统或计算机系统中,不同进程之间进行数据交换和通信的机制或技术。由于进程是操作系统中独立运行的程序实例,而进程间通信允许这些独立的进程之间相互协作、共享资源和进行数据交换。

为什么要进行进程间通信

        根据我们前面讲的,进程间是相互独立的,进程具有独立性啊,那通信不就不独立了吗?

    答案是正确的,进程通信的确会破坏进程的完全独立性,因为进程通信的目的是为了实现进程之间的数据共享、同步和协作。通过进程通信,各个进程可以相互交互和共享资源,这意味着它们不再完全独立,而是具有一定的相互依赖性和关联性。

        尽管进程通信破坏了进程的完全独立性,但这种破坏是有意义且必要的。在实际的计算机系统和操作系统中,进程往往需要协同工作、共享资源和交换数据才能完成复杂的任务。进程通信提供了一种机制,使得不同进程之间可以进行必要的协作和交流,并提供了相应的同步和保护机制来确保数据的正确性和一致性。

        所以这是一种权衡和折中的方案,但大部分情况下进程是相互独立的。


综上,进程间通信主要是为了完成下面这些作用:

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

进程间通信的本质理解

        1.我们知道进程具有独立性,是通过虚拟地址空间 + 页表映射的方式来保持独立性的,所以通信起来成本会比较高。

        2.既然通信,那么前提是一定要让不同的进程看到同一块“内存”(特定的结构组织),这块"内存"不能隶属于任何一个进程,而更应该强调共享

进程间通信不是目的,而是手段!


进程间通信的方式

        大体上可以分为3种通信方式:

  • 管道

    • 匿名管道pipe
    • 命名管道
  • System V IPC

    • System V消息队列
    • System V共享内存
    • System V信号量

System V只能用于单机通信(本地通信).

  • POSIX IPC

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

POSIX IPC可以在单机通信的基础上,进行网络通信(远程资源共享)。

        以上所提到的方式,我会在后面的章节逐一讲解,这个是进程间通信的方式.

今天我们将首要讲解进程间通信方式(一)——管道.

管道

什么是管道

        管道(Pipe)是一种进程间通信机制,用于在相关进程之间传输数据。它是一种特殊的文件描述符,它可以连接一个进程的输出(写入端)到另一个进程的输入(读取端),从而使得这两个进程可以通过管道进行数据传输。

        也就是说管道是单向传输的!现实生活中,我们所看听到的天然气管道、石油管道基本上都是单向传输的.

 匿名管道

什么是匿名管道

匿名管道(Anonymous Pipe)是进程间通信的一种机制,用于在具有亲缘关系(例如父子进程)或共享同一终端的兄弟进程之间传输数据。

        匿名管道是一种单向的数据流通道,它可以用于在进程之间传递数据。通常,一个进程作为管道的写入端(称为管道写入端),将数据写入管道;另一个进程作为管道的读取端(称为管道读取端),从管道中读取数据。

        匿名管道的创建是通过系统调用 pipe() 来完成的。pipe的使用后面会讲。

匿名管道通信的原理

        管道通信的背后是进程之间通过管道进行通信。

        我们知道一个进程要运行,首先要加载到内存,然后创建一个task_struct结构体,里面会有一个files_struct结构体,然后这个结构体里又有一个fd_array[]数组,每个元素指向对应的文件struct_file,里面包含了文件内容等.

        此时我们fork之后的子进程会重新创建一份task_struct,内容继承父进程的,此时fd_array[]里的内容也被子进程继承,即父进程打开的文件 子进程也继承了下来。它们指向的文件是 相同的.

        假设父进程3号文件描述符是读取文件的,4号文件描述符也用来写入文件的,子进程继承以后,fd=3也是用来读取文件的,fd=4也是用来写入文件的

         此时我们想让父进程进行写入fd=4,子进程进行读取fd=3,所以父进程就要关闭读端fd=3,子进程关闭写端fd=4。

这样我们就做到了不同的进程看到了同一份资源(通过fork子进程),而且通过文件描述符的方式完成了进程间的单向通信。

综上,管道内部本质大体是如下流程:

        1.父进程分别以读写方式打开一个文件

        2.fork()创建子进程

        3.双方各自关闭不需要的文件描述符

整体图如下:

pipe()的使用

        既然我们知道了思路,那我们可以用代码来使用一下管道。

        首先,父进程如何使用读和写方式分别打开文件呢?

这里使用到了pipe函数,函数用法及原型如下:

        参数pipefd为输出型参数,我们提前在外部定义好数组,然后传入,结果就会保存在这个数组中,分别为pipefd[0],代表的是读端,pipefd[1],代表的是写端.

        第二步我们利用fork创建子进程。

        最后,子进程用来读取文件的内容,并关闭写端pipefd[1],父进程用来写入内容,同时关闭读端。

匿名管道通信的特点

一个小demo如下:

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;int main()
{// 1.创建管道int pipefd[2] = {0}; // pipefd[0] :读端, pipefd[1] :写端int n = pipe(pipefd);assert(n != -1);#ifdef DEBUG#endif// cout << pipefd[0] << "  " << pipefd[1] << endl;// 2.创建子进程pid_t id = fork();assert(id != -1);if (id == 0){// 子进程// 3.构建单向通信的信道,父进程写入,子进程读取// 3.1关闭子进程不需要的fdclose(pipefd[1]);char buffer[1024];while (true){ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0;cout << "child get a message [" << getpid() << "] Father# " << buffer << endl;}}exit(0);}// 父进程// 构建单向通信的信道// 3.1 关闭父进程不需要的fdclose(pipefd[0]);string message = "我是父进程,我正在给你发消息";int count = 0;char send_buffer[1024];while (true){// 3.2 构建一个变化的字符串snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d", message.c_str(), getpid(), count++);// 3.3 写入write(pipefd[1], send_buffer, strlen(send_buffer));// 3.4 sleepsleep(1);}pid_t ret = waitpid(id, nullptr, 0);assert(ret > 0);close(pipefd[1]);return 0;
}

然后此时我们编译运行 :

 可以看到,父进程写入的内容,子进程全部读到了。        

这里是静态图,看不出效果,这些信息其实是时隔1秒打印一条的.

        我们看只有子进程在读取,可子进程我们并没有加任何的sleep啊,理论上应该子进程一直打印才对啊。

        显示器是一个文件,而管道也是一个文件,父子进程同时向显示器 写入时,没有说一个进程会等另一个进程,而是各自打印各自的消息,互相干扰,这是缺乏访问控制。而管道文件提供了访问控制,使得子进程读取完得等待父进程写入才能读取。

        这样,如果我们让子进程读取先sleep 10秒,期间父进程每隔1秒写入,等10秒过后,子进程开始读取,但会把 父进程写入10次的文件的内容全部一下读出来。这说明写入次数和读取次数没有直接关系。即管道是面向字节流的,具体怎么读需要定制协议,后面会说,

        这里就针对与管道的特点做一些总结:

  • 管道是用来进行具有血缘关系的进程进行进程间通信 --- 常用于父子间通信
  • 管道具有通过让进程间协同,提供了访问控制
  • 管道提供的是面向字节流式的通信服务 --- 面向字节流 --- 通过定制协议实现
  • 管道是基于文件的,文件的生命周期是随进程的,即管道的生命周期也随进程的!
  • 管道是单向通信的,就是半双工通信的一种特殊方式.

        上面最后一条提到了半双工概念,这里来解释一下:

        半双工通信的双方只能在同一时间点单向的传输数据,即两个参与者不能同时发送和接收数据。在半双工通信中,通信双方必须交替使用共享的通信信道。例如,当一个人在对讲机上说话时,另一个人必须停止接收,然后才能回应。典型的半双工通信方式包括对讲机和卫星电台。

        全双工:全双工通信允许在同一时间点双向地传输数据。这意味着通信的两个参与者能够同时发送和接收数据,而不需要交替使用通信信道。在全双工通信中,通信双方可以同时进行发送和接收操作,彼此之间的数据传输互不干扰。例如,电话通话是一个典型的全双工通信场景,双方可以同时说话和倾听对方的声音。

         顺带总结一下管道的几种情况:

        a.写快,读慢,写满就不能再写了

        b.写慢,读快,管道没有数据时,读必须等待

        这两种是由访问控制提供的.

        c.写关,读继续读,会标识读到了文件结尾

        d.写继续写,读关,OS会终止写进程

拓展代码

        利用匿名管道的方式,创建多个子进程,然后父进程分别派发随机的任务,

        总代码流程是:父进程首先load()加载方法,然后for循环创建多个进程,每次创建完成后,该进程都要与父进程(pipefd[1])建立关联,以方便父进程管理这些子进程。

        其中每个子进程调用 waitCommand函数,会阻塞在read,等待着父进程的写入,然后父进程开始分发任务,当是对应的子进程时,子进程会执行对应的任务,然后继续while循环等待。

共两个文件,第一个文件ProcessPool.cc文件

#include <iostream>
#include <vector>
#include <ctime>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"
using namespace std;#define PROCESS_NUM 5
int waitCommand(int waitFd, bool quit) // 如果对方不发任务就阻塞
{uint32_t command = 0;ssize_t s = read(waitFd, &command, sizeof(command));if (s == 0){quit = true;return -1;}assert(s == sizeof(command));return command;
}
void sendAndWakeup(pid_t who, int fd, uint32_t command)
{write(fd, &command, sizeof(command));cout << "main process: call process " << who << " execute " << desc[command] << " through " << fd << endl;
}int main()
{load();// pid : pipefdvector<pair<pid_t, int>> slots;// create multiple child processfor (int i = 0; i < PROCESS_NUM; i++){// 创建管道int pipefd[2] = {0};int n = pipe(pipefd);assert(n == 0);pid_t id = fork();assert(id != -1);// 让子进程读取if (id == 0){// 关闭写端close(pipefd[1]);// child processwhile (true){// 等命令bool quit = false;int command = waitCommand(pipefd[0], quit); // 如果对方不发任务就阻塞if (quit)break;// 执行对应的命令if (command >= 0 && command < handlerSize()){callbacks[command]();}else{cout << "非法 command" << endl;}}exit(1);}// father processclose(pipefd[0]);slots.push_back(pair<pid_t, int>(id, pipefd[1]));}// 父进程派发任务srand((unsigned long)time(nullptr) ^ getpid() ^ 2311156L);while (true){   //选择一个任务 int command = rand() % handlerSize();//选择一个进程,采用随机数的方式,选择进程来完成任务,随机数的方式负载均衡int choice = rand() % slots.size();// 把任务给指定的进程sendAndWakeup(slots[choice].first, slots[choice].second, command);sleep(1);int select;//以下是手动派发任务// int command = 0;// cout << "######################################" << endl;// cout << "1.show functions        2.send command" << endl;// cout << "######################################" << endl;// cout << "Please Select > ";// cin >> select;// if (select == 1)//     showHandler();// else if (select == 2)// {//     cout << "Enter your Command > ";//     // 选择任务//     cin >> command;//     // 选择进程//     int choice = rand() % slots.size();//     // 把任务给指定的进程//     sendAndWakeup(slots[choice].first, slots[choice].second, command);// }// else// {// }}// 关闭fd,结束所有进程for (auto &slot : slots){close(slot.second);}// 回收所有子进程for (auto &slot : slots){waitpid(slot.first, nullptr, 0);}return 0;
}

        第二个文件为Task.hpp文件,主要是包含了任务的加载及任务的执行方法。

        

#pragma once
#include<iostream>
#include<vector>
#include<string>
#include<unordered_map>
#include<unistd.h>
#include<functional>
using namespace std;typedef function<void()> func;vector<func> callbacks;
unordered_map<int,string> desc;void readMySQL()
{cout << "sub process[" << getpid() << "] 执行数据库被访问的任务\n" << endl;
}
void executeURL()
{cout << "sub process[" << getpid() << "] 执行url解析任务\n" << endl;
}
void cal()
{cout << "sub process[" << getpid() << "] 执行加密任务\n" << endl;
}
void save()
{cout << "sub process[" << getpid() << "] 执行数据持久化\n" << endl;
}void load()
{desc.insert({callbacks.size(),"readMySQWL:读取数据库"});callbacks.push_back(readMySQL);desc.insert({callbacks.size(),"executeURL:解析URL"});callbacks.push_back(executeURL);desc.insert({callbacks.size(),"cal:进行加密计算"});callbacks.push_back(cal);desc.insert({callbacks.size(),"save:进行数据的文件保存"});callbacks.push_back(save);
}
void showHandler()
{for(auto& iter: desc){cout << iter.first << "\t" << iter.second << endl;}
}
int handlerSize()
{return callbacks.size();
}

 

这样每次父进程都会随机给子进程派发随机的任务:

 

 命名管道

         与匿名管道不同,命名管道不需要亲缘关系的进程之间,也不需要共享同一终端。任意进程可以通过打开命名管道的读取端和写入端来与其进行通信。

什么是命名管道

命名管道(Named Pipe)是一种独立进程之间通信的机制,用于在无关的进程之间进行数据传输。

        命名管道通过在文件系统中创建一个特殊的文件来实现通信。这个特殊的文件被称为FIFO(First-in, First-out)或命名管道。

命名管道通信的原理

        和匿名管道一样,想让双方通信,必须先让双方看到同一份资源!它和匿名管道本质是一样的,只是看到资源的方式不同

        匿名管道是通过父子进程继承来看到同一份资源的,也叫做管道文件,这个文件是纯内存级的,所以没有名字,叫做匿名管道。

        而命名管道是在磁盘上有一个特殊的文件,这个文件可以被打开,但是打开后不会将内存中的数据刷新到磁盘。在磁盘上就有了路径,而路径是唯一的,所以双方就可以通过文件的路径 来看到同一份资源,即管道文件。

这是命名管道的流程:

  1. 创建命名管道:通过调用系统调用 mkfifo() 在文件系统中创建一个特殊的文件,这个文件就是命名管道。创建命名管道时,需要指定管道的名称和所需的权限。

  2. 打开命名管道:进程通过调用系统调用 open() 来打开命名管道,得到一个文件描述符。进程可以通过打开具有相同名称的文件来打开命名管道的读取端和写入端。

  3. 进程通信:一旦命名管道被打开,进程就可以使用文件描述符进行通信。每个进程可以选择读取端或写入端与命名管道进行交互。

  4. 数据传输:进程在读取端通过调用 read() 系统调用从命名管道中读取数据,而在写入端通过调用 write() 系统调用将数据写入命名管道中。读取端和写入端可以通过文件描述符进行数据的发送和接收。

  5. 关闭命名管道:进程完成通信后,可以通过调用 close() 关闭命名管道的文件描述符来释放资源。当所有对命名管道的引用都被关闭时,管道的文件系统条目将被删除。

mkfifo的使用

        上面提到了需要使用mkfifo来创建这个特殊的文件,来让独立的进程之间通过它进行通信,下面来看一下它的用法。

mkfifo [选项] 文件名

         非常简单的使用方法,选项我们一般不用,所以直接mkfifo + 文件名即可

        我们在当前路径下创建一个name_pipe的文件.

注意权限的最前面是p,代表是管道文件。


我们此时echo一句消息到这个管道文件中:

        我们发现这里阻塞住了,这是因为一方向管道文件里写入了,但是另外一方还没有读,所以此时我们新建一个窗口,然后读取name_pipe里的内容:

 

         这样信息便成功的被读取出来了,这就是mkfifo的简单使用。

代码模拟命名管道通信过程

        其实过程和匿名管道类似,只是看到同一资源的手段不一样。

        上面讲的mkfifo是指令创建,但是如果我想用代码该如何实现呢?这里有一个mkfifo函数:

         第一个参数是创建的管道文件的路径,第二个是权限。

         当创建成功时,mkfifo返回0,否则返回-1.

整体的流程是是这样的:

我们首先可以分成 服务端 和 客户端,服务端负责

        1.创建管道文件并打开

        2.进行与客户端正常的通信

        3.最后关闭并删除管道文件

而客户端

        1.首先要打开管道文件

        2.然后进行与服务端正常的通信流程即可

        这里为了方便,我们加入了日志,可以看到每一步的动作。

        所以一共四个文件,分别为comm.hpp,client.cc,server.cc,Log.hpp.

comm.hpp

#pragma once
#include<iostream>
#include<cstdio>
#include<cstring>
#include<unistd.h>
#include<vector>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>using namespace std;#define MODE 0666
#define SIZE 128
string ipcPath = "./fifo.ipc";

client.cc

#include "comm.hpp"
int main()
{//1.获取管道文件int fd = open(ipcPath.c_str(),O_WRONLY);if(fd < 0){perror("open");exit(1); }//2.通信过程string buffer;while(true){cout << "please Enter Message Line :> ";getline(cin,buffer);write(fd,buffer.c_str(),buffer.size());}//3.关闭文件return 0;
}

server.cc

#include"comm.hpp"
#include"Log.hpp"
int main()
{//1.创建管道文件if(mkfifo(ipcPath.c_str(),MODE) < 0){perror("mkfifo");exit(1);}Log("创建管道文件成功",Debug) << "step 1" << endl;//2.正常的文件操作int fd = open(ipcPath.c_str(),O_RDONLY);if(fd < 0){perror("open");exit(2);}Log("打开管道文件成功",Debug) << "step 2" << endl;//3.编写正常的通信代码char buffer[SIZE]; while(true){memset(buffer,'\0',sizeof(buffer));ssize_t s = read(fd,buffer,sizeof(buffer)-1);if(s > 0){cout << "client say> " << buffer << endl;}else if(s == 0){//end of filecerr << "read emd of file, client quit, server quit too!" << endl;break;}else{//read errorperror("read");}}//4.关闭文件close(fd);Log("关闭管道文件成功",Debug) << "step 3" << endl;unlink(ipcPath.c_str());//通信完毕就删除文件Log("删除管道文件成功",Debug) << "step 4" << endl;return 0;
}

Log.hpp

#pragma once
#include <iostream>
#include <ctime>
#include<string>
using namespace std;#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3string msg[] = {"Debug ","Notice","Warning","Error"
};ostream& Log(string message,int level)
{cout << " | " << (unsigned)time(NULL) << " | " << msg[level] << " | " << message;return cout;
}

 然后我们再编译运行,可以再创建一个Makefile文件,直接编译好所有的文件,内容如下:

.PHONY:all
all:client serverclient:client.ccg++ -o $@ $^ -std=c++11
server:server.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -rf client server

此时我们直接make即可。然后会得到两个可执行文件client和server.

我们打开两个窗口,首先运行server.

 第一步创建管道完成,然后我们在另一个窗口运行客户端.

运行起来后,显示打开文件也成功了,这个时候,我们在客户端输入,服务端都能读取到:

 然后我们ctrl + c 退出客户端,此时服务端也会break跳出循环,然后结束.

 这样,利用命名管道通信的代码流程也就完成了.

 

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

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

相关文章

ChatGPT结合知识图谱构建医疗问答应用 (二) - 构建问答流程

一、ChatGPT结合知识图谱 上篇文章对医疗数据集进行了整理&#xff0c;并写入了知识图谱中&#xff0c;本篇文章将结合 ChatGPT 构建基于知识图谱的问答应用。 下面是上篇文章的地址&#xff1a; ChatGPT结合知识图谱构建医疗问答应用 (一) - 构建知识图谱 这里实现问答的流程…

备忘录模式——撤销功能的实现

1、简介 1.1、概述 备忘录模式提供了一种状态恢复的实现机制&#xff0c;使得用户可以方便地回到一个特定的历史步骤。当新的状态无效或者存在问题时&#xff0c;可以使用暂时存储起来的备忘录将状态复原。当前很多软件都提供了撤销&#xff08;Undo&#xff09;操作&#xf…

Spring AOP

1.什么是 Spring AOP&#xff1f; AOP&#xff08;Aspect Oriented Programming&#xff09;&#xff1a;面向切面编程&#xff0c;它是⼀种思想&#xff0c;它是对某⼀类事情的集中处理。⽐如⽤户登录权限的效验&#xff0c;没学 AOP 之前&#xff0c;我们所有需要判断⽤户登…

ClickHouse(七):Clickhouse数据类型-2

进入正文前&#xff0c;感谢宝子们订阅专题、点赞、评论、收藏&#xff01;关注IT贫道&#xff0c;获取高质量博客内容&#xff01; &#x1f3e1;个人主页&#xff1a;含各种IT体系技术&#xff0c;IT贫道_Apache Doris,Kerberos安全认证,大数据OLAP体系技术栈-CSDN博客 &…

openlayers渲染rgb三波段cog时达到类似rgba的效果(去掉黑底)

图是arcgis渲染成rgb的&#xff0c;由于没有透明度波段&#xff0c;底下是黑的。 为了能在前端显示透明效果&#xff0c;之前是用python处理数据&#xff0c;给它加个透明度波段 后来研究了一下ol的样式表达式&#xff0c;可以直接在前端去掉黑底 样式设置代码如下 const s…

Socks IP轮换:为什么是数据挖掘和Web爬取的最佳选择?

在数据挖掘和Web爬取的过程中&#xff0c;IP轮换是一个非常重要的概念。数据挖掘和Web爬取需要从多个网站或来源获取数据&#xff0c;而这些网站通常会对来自同一IP地址的请求进行限制或封锁。为了避免这些问题&#xff0c;数据挖掘和Web爬取过程中需要使用Socks IP轮换技术。在…

云原生势不可挡,如何跳离云原生深水区?

云原生是云计算领域一大热词&#xff0c;伴随云原生概念而来的是数字产业迎来井喷、数字变革来临、数字化得以破局以及新一波的技术红利等等。云原生即“云”原生&#xff0c;顾名思义是让“应用”最大程度地利用云的能力&#xff0c;发挥云价值的最佳路径。具体来说&#xff0…

Eureka增加账号密码认证登录

一、业务背景 注册中心Eureka在微服务开发中经常使用到&#xff0c;用来管理发布的微服务&#xff0c;供前端或者外部调用。但是如果放到生产环境&#xff0c;我们直接通过URL访问的话&#xff0c;这显然是不安全的。 所以需要给注册中心加上登录认证。 通过账号和密码认证进行…

【机器学习】西瓜书习题3.5Python编程实现线性判别分析,并给出西瓜数据集 3.0α上的结果

参考代码 结合自己的理解&#xff0c;添加注释。 代码 导入相关的库 import numpy as np import pandas as pd import matplotlib from matplotlib import pyplot as plt导入数据&#xff0c;进行数据处理和特征工程 得到数据集 D { ( x i , y i ) } i 1 m , y i ∈ { 0 ,…

小程序商品如何设置限购

限购是一种常用的小程序商品销售策略&#xff0c;可以帮助商家提高销售额、控制库存和增加用户的购买欲望。那么&#xff0c;小程序产品怎么设置限购呢&#xff1f;下面将为您详细介绍。 1. 设置限购数量 可以设置最低购买数量来鼓励用户批量购买或满足特定的销售需求。例如&…

FFmpeg常见命令行(一):FFmpeg工具使用基础

前言 在Android音视频开发中&#xff0c;网上知识点过于零碎&#xff0c;自学起来难度非常大&#xff0c;不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》。本文是Android音视频任务列表的其中一个&#xff0c; 对应的要学习的内容是&#xff1a;FFmpe…

沙箱逃逸复现

当this指向window 原理 1.this直接指向window&#xff0c;拿到window的tostring的constructor来利用构造函数拿到process 是对象且指向沙箱外部&#xff0c;才可以利用 const vm require(vm); const script const process this.toString.constructor(return process)() pr…

OpenCL编程指南-9.1命令、队列、事件

概述 命令队列是OpenCL的核心。平台定义了一个上下文&#xff0c;其中包含一个或多个计算设备。每个计算设备可以有一个或多个命令队列。提交到这些队列的命令将完成OpenCL程序的具体工作。 在一个简单的OpenCL程序中&#xff0c;提交到一个命令队列的命令会按顺序执行。一个…

面试热题100(二叉树的右视图)

给定一个二叉树的 根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 树这类问题用的最多的就是递归&#xff0c;因为树具有天然的递归结构&#xff1a; 我们来分析一下题目&#xff0c;给定一棵树根结…

vue拖拽改变宽度

1.封装组件ResizeBox.vue <template><div ref"resize" class"resize"><div ref"resizeHandle" class"handle-resize" /><slot /></div> </template> <script> export default {name: Resi…

Python入门自学进阶-Web框架——38、redis、rabbitmq、git

缓存数据库redis&#xff1a; NoSQL&#xff08;Not only SQL&#xff09;泛指非关系型的数据库。为了解决大规模数据集合多重数据类的挑战。 NoSQL数据库的四大分类&#xff1a; 键值&#xff08;Key-Value&#xff09;存储数据库列存储数据库文档型数据库图形&#xff08;…

Spring学习笔记之spring概述

文章目录 Spring介绍Spring8大模块Spring特点 Spring介绍 Spring是一个轻量级的控制反转和面向切面的容器框架 Spring最初的出现是为了解决EJB臃肿的设计&#xff0c;以及难以测试等问题。 Spring为了简化开发而生&#xff0c;让程序员只需关注核心业务的实现&#xff0c;尽…

【暑期每日一练】 day14

目录 选择题 &#xff08;1&#xff09; 解析&#xff1a; &#xff08;2&#xff09; 解析&#xff1a; &#xff08;3&#xff09; 解析&#xff1a; &#xff08;4&#xff09; 解析&#xff1a; &#xff08;5&#xff09; 解析&#xff1a; 编程题 题一 …

品牌活动 | 阿里云云原生技术实践营:大模型+CloudOS,实现企业智能化

近日&#xff0c;由阿里云举办的“云原生技术实践营-应用和容器实践专场”在广州顺利开展。行云创新CEO马洪喜作为受邀嘉宾之一&#xff0c;参加了本次活动&#xff0c;分享了主题为“API大语言模型&#xff0c;以非侵入式实现企业业务智能化变革”的演讲&#xff0c;向参会者展…

Java正则校验密码至少包含:字母数字特殊符号中的2种

一、语法 字符说明\将下一字符标记为特殊字符、文本、反向引用或八进制转义符。例如&#xff0c; n匹配字符 n。\n 匹配换行符。序列 \\\\ 匹配 \\ &#xff0c;\\( 匹配 (。^匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性&#xff0c;^ 还会与"\n…