【Linux--进程间通信】

news2024/12/23 18:45:47

进程间通信介绍


进程间通信目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源
  • 通知事件:一个进程需要向另一个或一组进程发送消息。通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如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,意料之内用assert
    
    
    if(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.cc
	g++ -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,意料之内用assert
    
    
    if(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,意料之内用assert
    
    
    if(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,意料之内用assert
    
    
    if(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 0x6666

const 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)
{d
    return createShmHelper(k,size,IPC_CREAT);
}

#endif 

server.cc

#include"comm.hpp"


int main()
{
    //创建key
    key_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 server

client:client.cc
	g++ -o $@ $^ -std=c++11
server:server.cc
	g++ -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),使用者申请多少就给多少。
  • 我们在通信的时候,没有使用任何接口。一旦共享内存映射到进程的地址空间,该共享内存就直接被所有的进程直接看到了!由于这种特性,可以让进程通信的时候,减少拷贝次数,所以共享内存是所有进程间通信最快的一种。
  • 共享进程没有任何的保护机制(同步互斥),虽然速度快但是准确性会受到影响。
    在这里插入图片描述

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

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

相关文章

萤石网络发布家用及商用清洁机器人 积极布局具身智能

10月12日&#xff0c;萤石网络在杭州举行“智无感 净无忧——2023清洁机器人新品发布会”&#xff0c;带来了两款采用具身智能理念研发的清洁服务机器人新品。AI扫拖宝RS20 Pro支持AI智能融合避障&#xff0c;搭载主动切毛滚刷等创新技术&#xff0c;告别传统清洁困扰&#xff…

02Maven核心程序的下载与settings.xml文件的配置,环境变量的配置

Maven核心程序的解压与配置 Maven的下载与解压 Maven官网下载安装包 将下载的Maven核心程序压缩包apache-maven-3.8.4-bin.zip解压到一个非中文且没有空格的目录 Maven的核心配置文件 在Maven的解压目录conf中我们需要配置Maven的核心配置文件settings.xml 配置本地仓库位置…

Websocket升级版

之前写过一个关于websocket的博客&#xff0c;是看书时候做的一个demo。但是纸上得来终觉浅&#xff0c;这次实战后实打实的踩了不少坑&#xff0c;写个博客记录总结。 1.安装postman websocket接口调试&#xff0c;需要8.5以及以上版本的postman 先把以前的卸载&#xff0c…

CSS 中几种常用的换行方法

1、使用 br 元素&#xff1a; 最简单的换行方法是在需要换行的位置插入 元素。例如&#xff1a; <p>This is a sentence.<br>It will be on a new line.</p>这会在 “This is a sentence.” 和 “It will be on a new line.” 之间创建一个换行。 效果&a…

需永远在线的游戏公司,如何在线替换开源存储?

小帅是一个酷爱游戏的玩家&#xff0c;他玩一款游戏已经很久了&#xff0c;始终乐在其中。 这款游戏风靡全球&#xff0c;在中国手游出海榜单中&#xff0c;长期位居榜首。 他不知道的是&#xff0c;就在他玩游戏的过程中&#xff0c;这款游戏的出品公司&#xff0c;其实已经…

Nginx解析漏洞

常见的解析漏洞&#xff1a; IIS 5.x/6.0解析漏洞 IIS 7.0/IIS 7.5/ Nginx <0.8.3畸形解析漏洞 Nginx <8.03 空字节代码执行漏洞 Apache解析漏洞 Nginx文件解析漏洞 对于任意文件名&#xff0c;例如:cd.jpg在后面添加/x.php后&#xff0c;即可将文件作为php解析。 原理…

博客文档续更

十、 博客前台模块-异常处理 目前我们的项目在认证出错或者权限不足的时候响应回来的Json&#xff0c;默认是使用Security官方提供的响应的格式&#xff0c;但是这种响应的格式肯定是不符合我们项目的接口规范的。所以需要自定义异常处理 我们需要去实现AuthenticationEntryP…

使用pyinstaller打包Python程序

文章目录 背景操作步骤使用anaconda创建虚拟环境使用pyinstaller打包代码路径问题 程序相对路径参考文献 背景 当写好python文件后&#xff0c;会希望可以打包成可执行文件&#xff0c;这样对方不需要下载python&#xff0c;双击就可以执行&#xff0c;简单方便。 为了满足这…

使用 L293D 电机驱动器 IC 和 Arduino 控制直流电机

如果您打算组装新的机器人朋友&#xff0c;您最终会想要学习如何控制直流电机。控制直流电机最简单且经济的方法是将 L293D 电机驱动器 IC 与 Arduino 连接。它可以控制两个直流电机的速度和旋转方向。 此外&#xff0c;它还可以控制单极步进电机&#xff08;如 28BYJ-48&#…

【SQL】MySQL中的索引,索引优化

索引是存储引擎用来快速查询记录的一种数据结构&#xff0c;按实现方式主要分为Hash索引和B树索引。 按功能划分&#xff0c;主要有以下几类 单列索引指的是对某一列单独建立索引&#xff0c;一张表中可以有多个单列索引 1. 单列索引 - 普通索引&#xff1a;

ubuntu 安装jdk21开发环境

下载 wget https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.tar.gz 第二步&#xff1a;解压 tar -zxvf jdk-21_linux-x64_bin.tar.gz 第三步&#xff1a;移动 jdk-21 目录到 /usr/local/jdk21 第四步&#xff1a;配置环境变量 sudovim/etc/profile vim/etc/…

手部关键点检测2:YOLOv5实现手部检测(含训练代码和数据集)

手部关键点检测2&#xff1a;YOLOv5实现手部检测(含训练代码和数据集) 目录 手部关键点检测2&#xff1a;YOLOv5实现手部检测(含训练代码和数据集) 1. 前言 2. 手部检测数据集说明 &#xff08;1&#xff09;手部检测数据集 &#xff08;2&#xff09;自定义数据集 3. 基…

python入门篇08- 函数进阶-参数传递

全文目录,一步到位 1.前言简介1.1 专栏传送门1.1.1 上文小总结1.1.2 上文传送门 2. python基础使用2.1 函数进阶 - 参数传递2.1.1 设置多个返回值 2.2 传参方式(多种)2.1.0 代码准备2.1.1 方式一: 参数位置传递2.1.2 方式二: 关键字参数传递2.1.3 方式三: 缺省参数传递2.1.4 方…

口袋参谋:淘宝卖家必备的市场调查分析工具!

​在淘宝天猫上开店&#xff0c;首先想到的第一个问题就是——卖什么&#xff1f; 想要解决这个疑问&#xff0c;我们就需要对一些你选的品类做市场调查&#xff0c;根据市场调查分析得出了结论&#xff0c;哪个市场竞争力小&#xff0c;那就卖哪个&#xff01; 卖家做市场调查…

Linux内存管理 (2):memblock 子系统的建立

前一篇&#xff1a;Linux内存管理 (1)&#xff1a;内核镜像映射临时页表的建立 文章目录 1. 前言2. 分析背景3. memblock 简介3.1 memblock 数据结构3.2 memblock 接口 4. memblock 的构建过程 1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&#xff0c;因此而给读者…

【动态规划】121. 买卖股票的最佳时机、122. 买卖股票的最佳时机 II

提示&#xff1a;努力生活&#xff0c;开心、快乐的一天 文章目录 121. 买卖股票的最佳时机&#x1f4a1;解题思路&#x1f914;遇到的问题&#x1f4bb;代码实现&#x1f3af;题目总结 122. 买卖股票的最佳时机 II&#x1f4a1;解题思路&#x1f914;遇到的问题&#x1f4bb;代…

DC电源模块工作效率的特点

BOSHIDA DC电源模块工作效率的特点 DC电源模块是一种常见的电源供应装置&#xff0c;它在广泛应用于各种电子设备中。它是一种直流电源&#xff0c;通常用于提供低压、高电流的电源&#xff0c;如电子器件、LED灯、无线路由器、计算机硬件等。DC电源模块的工作效率是其中一个非…

关于如何进行ChatGPT模型微调的新手指南

微调是指在预训练的模型基础上&#xff0c;通过进一步的训练来调整模型以适应特定任务或领域。预训练的模型在大规模的文本数据上进行了广泛的学习&#xff0c;从中获得了一定的知识和语言理解能力。然而&#xff0c;由于预训练并不针对具体任务&#xff0c;因此需要微调来使模…

启山智软/微信小程序商城

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 启山智软一、微信小程序商城二、微信小程序商城的定义微信小程序商城特点总结 启山智软 想要了解代码规范&#xff0c;学习商城解决方案&#xff0c;点击下方官网链…

11.1.0- iDesktopX新特性之统计面内对象数

作者&#xff1a;Mei 文章目录 一、属性更新二、面内统计对象数 当我们在做数据处理时&#xff0c;可能会遇到需要统计面内包含其他对象数的需求&#xff0c;在以往的iDesktopX 11i版本中&#xff0c;一般是用属性更新功能。今年发布的iDesktopX 11.1.0版本&#xff0c;有一个新…