当前位置: 首页 > news >正文

【Linux--进程间通信】

进程间通信介绍


进程间通信目的

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

进程间通信发展

管道

  • 匿名管道pipe
  • 命名管道

system V IPC

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

POSIX IPC

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

管道

什么是管道

管道是Unix中最古老的进程间通信
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。
在这里插入图片描述

匿名管道

mypipe.cc

#include<iostream>
#include<unistd.h>
#include<cerrno>
#include<string.h>
#include<assert.h>
#include<unistd.h>int main()
{//让不同的进程看到同一份资源!!!//任何一种进程间通信中,一定要先保证不同的进程之间看到同一份资源int pipefd[2] = {0};//1、创建管道int n = pipe(pipefd);if(n<0){std::cout<<"pipe error"<<errno<<":"<< strerror(errno)<<std::endl;return 1;}std::cout<<"pipefd[0]:"<<pipefd[0]<<std::endl;//读端 ->嘴巴->读书std::cout<<"pipefd[1]:"<<pipefd[1]<<std::endl;//写端 ->笔->写 //2、创建子进程pid_t id = fork();assert(id!=-1);//正常应该用判断,这里就直接断言了;意外之外用if,意料之内用assertif(id ==0)//子进程{//3、关闭不需要的fd,让父进程去读,子进程去写close(pipefd[0]);//4、开始通信--结合某种场景const std::string namestr="hello 我是子进程";int cnt = 1;char buffer[1024];while(true){snprintf(buffer,sizeof(buffer),"%s,计数器:%d,我的id:,%d",namestr.c_str(),cnt++,getpid());write(pipefd[1],buffer,strlen(buffer));sleep(1);}close(pipefd[1]);exit(0);}//父进程//3、关闭不需要的fd,让父进程去读,子进程去写close(pipefd[1]);//4、开始通信--结合某种场景char buffer[1024] ;while(true){sleep(1);int n = read(pipefd[0],buffer,sizeof(buffer));if(n>0){buffer[n]='\0';std::cout<<"我是父进程,child give me message: "<<buffer<<std::endl;}}return 0;
}

Makefile

mypipe:mypipe.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -rf mypipe

运行结果
在这里插入图片描述
可以看到父进程读到了子进程中的内容,同时父进程读取的速度和写入的速度是相同的。

管道的特点

  1. 单向通信
  2. 管道的本质是文件,因为fd的生命周期随进程,管道的生命周期是随进程的。
  3. 管道通信,通常用来进行具有“血缘”关系的进程,进行程序间通信,常用于父子通信–pipe打开管道,并不清楚管道的名字,叫做匿名管道。
  4. 在管道通信中,写入的次数和读取的次数,不是严格匹配的,读写次数的多少没有强相关–表现–字节流

4种场景

  1. 如果我们read读取完毕了所有的管道数据,如果对方不发,我就只能等待。
  2. 如果我们write端将管写满了,我们就不能继续写了。
#include<iostream>
#include<unistd.h>
#include<cerrno>
#include<string.h>
#include<assert.h>
#include<unistd.h>int main()
{//让不同的进程看到同一份资源!!!//任何一种近侧和你关键通信中,一定要先保证不同的进程之间看到同一份资源int pipefd[2] = {0};//1、创建管道int n = pipe(pipefd);if(n<0){std::cout<<"pipe error"<<errno<<":"<< strerror(errno)<<std::endl;return 1;}std::cout<<"pipefd[0]:"<<pipefd[0]<<std::endl;//读端 ->嘴巴->读书std::cout<<"pipefd[1]:"<<pipefd[1]<<std::endl;//写端 ->笔->写 //2、创建子进程pid_t id = fork();assert(id!=-1);//正常应该用判断,这里就直接断言了;意外之外用if,意料之内用assertif(id ==0)//子进程{//3、关闭不需要的fd,让父进程去读,子进程去写close(pipefd[0]);//4、开始通信--结合某种场景// const std::string namestr="hello 我是子进程";// int cnt = 1;// char buffer[1024];int cnt = 0;while(true){// snprintf(buffer,sizeof(buffer),"%s,计数器:%d,我的id:,%d",namestr.c_str(),cnt++,getpid());// write(pipefd[1],buffer,strlen(buffer));//sleep(1);char x ='X';write(pipefd[1],&x,1);std::cout<<"cnt:"<<cnt++<<std::endl;}close(pipefd[1]);exit(0);}//父进程//3、关闭不需要的fd,让父进程去读,子进程去写close(pipefd[1]);//4、开始通信--结合某种场景char buffer[1024] ;while(true){sleep(1);int n = read(pipefd[0],buffer,sizeof(buffer));if(n>0){buffer[n]='\0';std::cout<<"我是父进程,child give me message: "<<buffer<<std::endl;}}return 0;
}

运行结果:
在这里插入图片描述
3. 如果我们关闭了写端,读取完毕管道数据,再读就会read返回0,表明读到了文件结尾。

#include<iostream>
#include<unistd.h>
#include<cerrno>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<sys/wait.h>int main()
{//让不同的进程看到同一份资源!!!//任何一种近侧和你关键通信中,一定要先保证不同的进程之间看到同一份资源int pipefd[2] = {0};//1、创建管道int n = pipe(pipefd);if(n<0){std::cout<<"pipe error"<<errno<<":"<< strerror(errno)<<std::endl;return 1;}std::cout<<"pipefd[0]:"<<pipefd[0]<<std::endl;//读端 ->嘴巴->读书std::cout<<"pipefd[1]:"<<pipefd[1]<<std::endl;//写端 ->笔->写 //2、创建子进程pid_t id = fork();assert(id!=-1);//正常应该用判断,这里就直接断言了;意外之外用if,意料之内用assertif(id ==0)//子进程{//3、关闭不需要的fd,让父进程去读,子进程去写close(pipefd[0]);//4、开始通信--结合某种场景// const std::string namestr="hello 我是子进程";// int cnt = 1;// char buffer[1024];int cnt = 0;while(true){// snprintf(buffer,sizeof(buffer),"%s,计数器:%d,我的id:,%d",namestr.c_str(),cnt++,getpid());// write(pipefd[1],buffer,strlen(buffer));//sleep(1);char x ='X';write(pipefd[1],&x,1);std::cout<<"cnt:"<<cnt++<<std::endl;sleep(1);break;}close(pipefd[1]);exit(0);}//父进程//3、关闭不需要的fd,让父进程去读,子进程去写close(pipefd[1]);//4、开始通信--结合某种场景char buffer[1024] ;int cnt = 0;while(true){int n = read(pipefd[0],buffer,sizeof(buffer)-1);if(n>0){buffer[n]='\0';std::cout<<"我是父进程,child give me message: "<<buffer<<std::endl;}else if(n==0){std::cout<<"我是父进程,读到了文件结尾"<<std::endl;break;}else{std::cout<<"我是父进程,读异常了"<<std::endl;break;}sleep(1);if(cnt++>5) break;}close(pipefd[0]);int status = 0;waitpid(id,&status,0);std::cout<<"sig:"<<(status & 0x7F)<<std::endl;//sleep(100);return 0;
}

运行结果:
在这里插入图片描述

  1. 如果写端一直写,读端关闭会发生什么呢?没有意义,操作系统不会维护无意义、低效率或者浪费资源的事情。OS会杀死一直在写入的进程!OS会通过信号来终止进程。13)SIGPIPE
#include<iostream>
#include<unistd.h>
#include<cerrno>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<sys/wait.h>int main()
{//让不同的进程看到同一份资源!!!//任何一种近侧和你关键通信中,一定要先保证不同的进程之间看到同一份资源int pipefd[2] = {0};//1、创建管道int n = pipe(pipefd);if(n<0){std::cout<<"pipe error"<<errno<<":"<< strerror(errno)<<std::endl;return 1;}std::cout<<"pipefd[0]:"<<pipefd[0]<<std::endl;//读端 ->嘴巴->读书std::cout<<"pipefd[1]:"<<pipefd[1]<<std::endl;//写端 ->笔->写 //2、创建子进程pid_t id = fork();assert(id!=-1);//正常应该用判断,这里就直接断言了;意外之外用if,意料之内用assertif(id ==0)//子进程{//3、关闭不需要的fd,让父进程去读,子进程去写close(pipefd[0]);//4、开始通信--结合某种场景// const std::string namestr="hello 我是子进程";// int cnt = 1;// char buffer[1024];int cnt = 0;while(true){// snprintf(buffer,sizeof(buffer),"%s,计数器:%d,我的id:,%d",namestr.c_str(),cnt++,getpid());// write(pipefd[1],buffer,strlen(buffer));//sleep(1);char x ='X';write(pipefd[1],&x,1);std::cout<<"cnt:"<<cnt++<<std::endl;sleep(1);//break;}close(pipefd[1]);exit(0);}//父进程//3、关闭不需要的fd,让父进程去读,子进程去写close(pipefd[1]);//4、开始通信--结合某种场景char buffer[1024] ;int cnt = 0;while(true){int n = read(pipefd[0],buffer,sizeof(buffer)-1);if(n>0){buffer[n]='\0';std::cout<<"我是父进程,child give me message: "<<buffer<<std::endl;}else if(n==0){std::cout<<"我是父进程,读到了文件结尾"<<std::endl;break;}else{std::cout<<"我是父进程,读异常了"<<std::endl;break;}sleep(1);if(cnt++>5) break;}close(pipefd[0]);int status = 0;waitpid(id,&status,0);std::cout<<"sig:"<<(status & 0x7F)<<std::endl;sleep(100);return 0;
}

运行结果:

在这里插入图片描述
可以看到子进程退出后,由父进程通过waitpid读到子进程退出码。

管道特点

  • 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可应用该管道。
  • 管道提供流式服务。
  • 一般而言,进程退出,管道释放,所以管道的生命周期随进程。
  • 一般而言,内核会对管道操作进行同步与互斥。
  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道。

命名管道

  • 管道应用的一个限制就是只能再基友共同祖先(具有亲缘关系)的进程间通信。
  • 如果我们想不在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它被称为命名管道。
  • 命名管道是一种特殊类型的文件。

创建一个命名管道

命名管道可以从命令行上创建,命令行方式是使用下面这个方式。

mkfifo filename

命名管道也可以从程序里面创建,相关函数有:

int mkfifo(const char *filename,mode_t mode);

创建命名管道:

int main(int argc,char *argv[])
{mkfifo("p2",0644);return 0;
}

匿名管道与命名管道的区别

  • 匿名管道由pipe函数创建并打开
  • 命名管道由mkfifo函数创建,打开用open
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在于他们创建与打开的方式不同,一旦这些工作完成之后,他们具有相同的语义。

命名管道的打开规则

如果当前打开操作是为读而打开FIFO时

  • O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
  • O_NONBLOCK enable:立刻返回成功
    如果当前打开操作是为写而打开FIFO时
  • O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
  • O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

system V共享内存

共享内存是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不在涉及到 内核,换句话说是进程不在通过执行进入内核的系统调用来传递彼此的数据。
共享内存示意图
在这里插入图片描述

共享内存数据结构

共享内存函数
shmget函数

功能:用来创建共享内存
原型:int shmget(key_t key,size_t size,int shmflg);
参数

  • key(共享内存段名字)
  • size(共享内存大小)
  • shmflg(由九个权限标志构成,他们的用法和创建文件时使用的mode模式标志是一样的

返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

shmat函数

功能:将共享内存段连接到地址空间
原型:void *shmat(int shmid,const void *shmaddr,int shmflg);
参数

  • shmid:共享内存标识
  • shmaddr:指定连接的地址
  • shmflg:它的两个可能取值是SHM_RND 和SHM_RDONLY

返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
说明

  • shmaddr为NULL,核心自动选择一个地址
  • shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址
  • shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr(shmaddr % SHMLBA)
  • shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

进程间通信的前提是让不同的进程看到同一份资源
ipcs函数
在这里插入图片描述

下面来看一段代码
comm.hpp

#ifndef __COM_HPP__#define __COM_HPP__#include<iostream>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<cerrno>
#include<cstring>
#include<cstdio>
#include<string>using namespace std;//单独使用IPC_CREAT:创建一个共享内存,如果共享内存不存在,就创建;如果已经存在获取已经存在的共享内存并返回
//IPC_EXCL不能单独使用,一般都要配合IPC_CREAT
//IPC_CREAT|IPC_EXCL:创建一个共享内存,如果共享内存不存在,就创建;如果已经存在,就立马出错返回//只要没出错就是成功创建了新的共享内存#define PATHNAME "."
#define PROID 0x6666const int gsize=4096;//字节为单位
key_t getkey()
{key_t k = ftok(PATHNAME,PROID);if(k==-1){cerr<<"error:"<<errno<<":"<<strerror(errno)<<endl;exit(1);}return k;
}
string toHex(int x)
{char buffer[64];snprintf(buffer,sizeof(buffer),"0x%x",x);return buffer;
}
static int createShmHelper(key_t k,int size,int flag)
{int shmid = shmget(k,gsize,flag);if(shmid==-1){cerr<<"error:"<<errno<<":"<<strerror(errno)<<endl;exit(2);}return shmid;
}
int createShm(key_t k,int size)
{return createShmHelper(k,size,IPC_CREAT|IPC_EXCL);
}
int getShm(key_t k,int size)
{dreturn createShmHelper(k,size,IPC_CREAT);
}#endif 

server.cc

#include"comm.hpp"int main()
{//创建keykey_t k = getkey();cout<<"server key:"<<toHex(k)<<endl;//2、创建共享内存int shmid = createShm(k,gsize);cout<<"server shmid:"<<toHex(shmid)<<endl;return 0;
}

client.cc

#include"comm.hpp"int main()
{key_t k = getkey();cout<<"client key:"<<toHex(k)<<endl;int shmid = getShm(k,gsize);cout<<"client shmid:"<<toHex(shmid)<<endl;return 0;
}

Makefile

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

编译运行
在这里插入图片描述

通过ipcs查看信息
在这里插入图片描述
当我们重新编译运行程序时,我们看到打印了错误信息,并且返回值为2

在这里插入图片描述
也就是说共享内存创建失败
在这里插入图片描述
进一步分析我们不难得知是因为共享内存在进程关闭之后还存在,所以无法创建新的。
即是共享内存生命周期不随进程、随OS
在这里插入图片描述

那么我们应该怎么删除共享内存呢?
ipcrm
在这里插入图片描述
我们可以用shmid删除共享内存
在这里插入图片描述
我们也可以使用key来删除共享内存

在这里插入图片描述

key VS shmid

keyshmid
类比:文件系统里的inode类比:文件系统里的fd
系统层面用户层面

shmctl函数

功能:用于控制共享内存
原型:int shmctl(int shm,int cmd,struct shmid_ds *buf);
参数

  • shmid:由shmget返回的共享内存标识码
  • cmd:将要采取的动作(有三个可取值)
  • buf:指向一个保存着共享内存的模式状态和访问权限的数据结构

返回值:成功返回0,失败返回-1

命令说明
IPC_STAT把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值
IPC_RMID删除共享内存段

注意

  • 由于共享内存大小是向上对齐PAGE(4KB),使用者申请多少就给多少。
  • 我们在通信的时候,没有使用任何接口。一旦共享内存映射到进程的地址空间,该共享内存就直接被所有的进程直接看到了!由于这种特性,可以让进程通信的时候,减少拷贝次数,所以共享内存是所有进程间通信最快的一种。
  • 共享进程没有任何的保护机制(同步互斥),虽然速度快但是准确性会受到影响。
    在这里插入图片描述

相关文章:

【Linux--进程间通信】

进程间通信介绍 进程间通信目的 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程资源共享&#xff1a;多个进程之间共享同样的资源通知事件&#xff1a;一个进程需要向另一个或一组进程发送消息。通知它&#xff08;它们&#xff09;发生了某种事件&#xff08;如…...

Linux C文件操作

文章目录 文件操作函数文件系统调用系统调用与标准函数c的调用的区别文件的读取位置标准c函数系统调用空洞文件 文件的内存映射操作文件目录 linux下的文件操作包括两种&#xff0c;一种是使用C函数&#xff0c;一种是使用系统调用。 gcc 常用来实现c程序的编译gcc filename.c …...

基于Cucumber的行为驱动开发(BDD)实例

本篇介绍 Cucumber 的基本使用&#xff0c; 因为Cucumber是BDD的工具&#xff0c; 所以首先需要弄清楚什么是BDD&#xff0c;而在介绍BDD之前&#xff0c;先看看常见的软件开发方法。 常见的软件开发方法 面向过程开发&#xff08;Procedural Development&#xff09;&#x…...

十六、代码校验(2)

本章概要 前置条件 断言&#xff08;Assertions&#xff09;Java 断言语法Guava 断言使用断言进行契约式设计检查指令前置条件后置条件不变性放松 DbC 检查或非常严格的 DbCDbC 单元测试 前置条件 前置条件的概念来自于契约式设计(Design By Contract, DbC), 利用断言机制…...

安卓 kotlin-supportFragmentManager报红

如果你继承baseActivity 请查看 是不是继承 AppCompatActivity...

linux中安装RocketMQ以及dashboard

前提&#xff1a; 需要安装jdk8 上传下面的文件到服务器中 新建目录 mkdir rocketmq 将下载后的压缩包上传到阿里云服务器或者虚拟机中去&#xff0c;并解压 unzip rocketmq-all-4.9.2-bin-release.zip 配置环境变量 vim /etc/profile 配置内容&#xff1a; export NAM…...

Android kotlin内联函数(inline)的详解与原理

一、介绍 在kotlin中&#xff0c;有一种函数叫内联函数&#xff0c;这种函数标识符是inline&#xff0c;但是好多人对这个函数的理解只停留在八股文中&#xff0c;内容函数的用法和普通函数没有区别&#xff0c;但是在编译原理上是有&#xff0c;对程序的性能有一定的影响。 二…...

林沛满---一个面试建议

在应聘一个技术职位之前&#xff0c;做好充分的准备无疑能大大提高成功率。这里所说的准备并不是指押题&#xff0c;因为有经验的面试官往往准备了海量的题库&#xff0c;押中的概率太低。比如我有位同事的题库里有上百道题&#xff0c;内容涵盖了编程、操作系统、网络、存储……...

CMake教程-第 5 步:安装和测试

CMake教程-第 5 步&#xff1a;安装和测试 1 CMake教程介绍2 学习步骤Step 1: A Basic Starting PointStep 2: Adding a LibraryStep 3: Adding Usage Requirements for a LibraryStep 4: Adding Generator ExpressionsStep 5: Installing and TestingStep 6: Adding Support f…...

移动应用-Android开发基础\核心知识点

Android开发基础 知识点 1 介绍了解2 系统体系架构3 四大应用组件4 移动操作系统优缺点5 开发工具6 配置工具7 下载相关资源8JDK下载安装流程9配置好SDK和JDK环境10 第一个Hello word11 AS开发前常用设置12模拟器使用运行13 真机调试14 AndroidUI基础布局15 加载展示XML布局16…...

Java读取并转换字符串中的浮点数

在写Android接收蓝牙数据的时候&#xff0c;由于传过来的蓝牙数据转换后都为字符串格式&#xff0c;但是需要从其中提取出来浮点数&#xff0c;所以通过查阅资料写出了从字符串中提取并转换为浮点数的方法&#xff0c;特记录下来以供参考。 目录 原始数据内容 提取字符串中的…...

SQL: 索引原理与创建索引的规范

SQL 索引是一种数据结构&#xff0c;用于加速数据库查询操作。它通过在表的列上创建索引&#xff0c;提供了一种快速查找数据的方法&#xff0c;减少了数据库的扫描和比较操作&#xff0c;从而提高了查询性能。索引根据其实现方式可以分为多种类型&#xff0c;如 B-树索引、哈希…...

基于STM32_DS18B20单总线传感器驱动

基于STM32_DS18B20单总线传感器驱动 文章目录 基于STM32_DS18B20单总线传感器驱动前言一、BS18B20&#xff1f;二、原理1.复位与检验2.基本命令3.唯一ROM识别码4.温度转换 三、驱动代码四、注意事项 前言 本文以一款典型的单总线传感器及其驱动——DS18B20为例&#xff0c;简单…...

目标识别项目实战:基于Yolov7-LPRNet的动态车牌目标识别算法模型(三)

前言 目标识别如今以及迭代了这么多年&#xff0c;普遍受大家认可和欢迎的目标识别框架就是YOLO了。按照官方描述&#xff0c;YOLOv8 是一个 SOTA 模型&#xff0c;它建立在以前 YOLO 版本的成功基础上&#xff0c;并引入了新的功能和改进&#xff0c;以进一步提升性能和灵活性…...

springboot线程池创建与使用

/*** author: zcs* Title: TaskPoolConfig* Description: 线程池配置* date: 2023/10/11 17:52*/ Component public class TaskPoolConfig {Bean(name "threadPoolTaskExecutor")public Executor taskExecutor() {ThreadPoolTaskExecutor taskExecutor new ThreadP…...

UDP和TCP特点(部分)对比:

传输层的两个主要协议&#xff1a;TCP 和 UDP UDP和TCP特点&#xff08;部分&#xff09;对比&#xff1a; UDP&#xff1a;无连接&#xff0c; 不可靠传输&#xff0c; 面向数据报&#xff0c; 全双工。 TCP&#xff1a;有连接&#xff0c; 可靠传输&#xff0c; 面向字节流…...

PostMan环境变量、全局变量、动态参数使用

一、环境准备 postmanmoco [{"description": "登录认证","request": {"uri": "/login","method": "post","forms": {"user": "admin","password": "a123…...

服务器数据恢复-服务器硬盘指示灯黄灯闪烁的数据恢复案例

服务器数据恢复环境&#xff1a; 服务器面板上的硬盘指示灯显示黄色是一种警告&#xff0c;提示指示灯对应的服务器硬盘已经被服务器识别出存在故障&#xff0c;硬盘即将下线。如果出现这种情况&#xff0c;建议服务器管理员/运维人员及时用完好的硬盘替换显示黄色指示灯对应的…...

ts 分发

在 TypeScript 中&#xff0c;"分发"&#xff08;distributive&#xff09;是指在条件类型中的联合类型上自动进行类型推断的机制。当使用条件类型操作联合类型时&#xff0c;TypeScript 会自动将联合类型中的每个成员都应用该条件类型。 下面是一个示例&#xff1a…...

SQL中的group by使用注意事项

在 SQL 中&#xff0c;GROUP BY 语句用于将查询结果按照指定的列进行分组&#xff0c;并对每个分组计算聚合函数&#xff08;如 SUM、AVG、COUNT 等&#xff09;的值。一般情况下&#xff0c;如果查询中包含聚合函数&#xff0c;那么就需要使用 GROUP BY 语句将查询结果按照指定…...

1688商品列表API与其他数据源的对接思路

将1688商品列表API与其他数据源对接时&#xff0c;需结合业务场景设计数据流转链路&#xff0c;重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点&#xff1a; 一、核心对接场景与目标 商品数据同步 场景&#xff1a;将1688商品信息…...

根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:

根据万维钢精英日课6的内容&#xff0c;使用AI&#xff08;2025&#xff09;可以参考以下方法&#xff1a; 四个洞见 模型已经比人聪明&#xff1a;以ChatGPT o3为代表的AI非常强大&#xff0c;能运用高级理论解释道理、引用最新学术论文&#xff0c;生成对顶尖科学家都有用的…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词

Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵&#xff0c;其中每行&#xff0c;每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid&#xff0c;其中有多少个 3 3 的 “幻方” 子矩阵&am…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法&#xff0c;当前调用一个医疗行业的AI识别算法后返回…...

深度学习习题2

1.如果增加神经网络的宽度&#xff0c;精确度会增加到一个特定阈值后&#xff0c;便开始降低。造成这一现象的可能原因是什么&#xff1f; A、即使增加卷积核的数量&#xff0c;只有少部分的核会被用作预测 B、当卷积核数量增加时&#xff0c;神经网络的预测能力会降低 C、当卷…...

MySQL 知识小结(一)

一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库&#xff0c;分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷&#xff0c;但是文件存放起来数据比较冗余&#xff0c;用二进制能够更好管理咱们M…...

根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要

根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分&#xff1a; 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用

在工业制造领域&#xff0c;无损检测&#xff08;NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统&#xff0c;以非接触式光学麦克风技术为核心&#xff0c;打破传统检测瓶颈&#xff0c;为半导体、航空航天、汽车制造等行业提供了高灵敏…...

Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?

Pod IP 的本质与特性 Pod IP 的定位 纯端点地址&#xff1a;Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址&#xff08;如 10.244.1.2&#xff09;无特殊名称&#xff1a;在 Kubernetes 中&#xff0c;它通常被称为 “Pod IP” 或 “容器 IP”生命周期&#xff1a;与 Pod …...

论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving

地址&#xff1a;LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂&#xff0c;正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...