Linux 进程间通信 System V系列: 共享内存,信号量,简单介绍消息队列

进程间通信 System V系列: 共享内存,初识信号量

  • 一.共享内存
    • 1.引入
    • 2.原理
    • 3.系统调用接口
      • 1.shmget
      • 2.shmat和shmdt
      • 3.shmctl
    • 4.边写代码边了解共享内存的特性
      • 1.ftok形成key,shmget创建与获取共享内存
      • 2.shm相关指令
      • 3.shmat和shmdt挂接和取消挂接
      • 4.shmctl获取共享内存信息,释放共享内存
      • 5.开始通信
    • 5.利用管道实现共享内存的协同机制
      • 1.Sync(同步类)
      • 2.读写端的修改
      • 3.动图演示
    • 6.共享内存的优缺点
  • 二.消息队列
    • 1.概念
    • 2.接口,数据结构等等
  • 三.信号量理论
    • 1.信号量的原理
    • 2.信号量的理论
      • 1.从生活中的例子理解信号量
      • 2.进程角度的信号量
      • 3.信号量的细节
        • 1.信号量必须要由OS提供并维护
        • 2.信号量的基本操作
    • 3.信号量的接口
      • 1.semget
      • 2.semctl
      • 3.semop
  • 四.System V系列的进程间通信的小总结
  • 五.利用信号量实现共享内存的协同机制
    • 1.思路
    • 2.Server创建并获取信号量,Client获取信号量 -> ftok和semget
      • 1.ftok
      • 2.shmget
    • 3.Server阻塞申请信号量资源 - semop
    • 4.Client初始化信号量资源 - semctl
    • 5.Server释放信号量资源 - semctl
    • 6.完整代码
      • 1.Common.hpp
      • 2.sem.hpp
      • 3.ShmServer.cpp
      • 4.ShmClient.cpp
    • 7.演示

我们不是都有管道了吗?为什么还要有其他的进程间通信方式呢?
当时的年代,通信技术是一个非常火的点,就像现在人工智能和各种大模型一样,类似于百家争鸣的样子,所以有很多进程间通信的方式

因为共享内存跟我们学的进程地址空间有密切联系,所以我们重点学习
而信号量我们就先认识一下,学习一下理论即可

一.共享内存

1.引入

管道方便是方便,直接复用文件接口即可,但是想要使用管道是需要访问内核的,而且管道的内核级缓冲区也是在内核当中的,因此会导致效率不是特别好(因为访问内核本身就是一个比较大的消耗)

那么有没有什么办法能够让两个进程无需访问内核就能进行进程间通信呢?
在这里插入图片描述

2.原理

跟命名管道一样,共享内存也是允许完全无关的两个进程商量一下一起使用同一份资源,从而实现进程间通信的
在这里插入图片描述
看似很好懂,但是有几个值得关注的点:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.系统调用接口

shm就是shared_memory:共享内存
ipc: InterProcess Communication:进程间通信

1.shmget

在这里插入图片描述
第三个参数为什么要这么设计呢?
我们一起来分析一下
在这里插入图片描述
我们刚才还没有说返回值
在这里插入图片描述

2.shmat和shmdt

分配完一个共享内存了,下面要做的事情就是把共享内存映射到进程的进程地址空间当中,并用页表建立该映射

shmat:shmattach是负责建立映射的,也就是将共享内存和进程挂接起来
shmdt:shmdetach(detach是分离,拆卸的意思),也就是取消该共享内存跟进程的挂接关系
在这里插入图片描述
shmdt直接传入shmat的返回值即可
在这里插入图片描述
shmat:如果挂接失败,返回(void*)-1

3.shmctl

在这里插入图片描述

4.边写代码边了解共享内存的特性

1.ftok形成key,shmget创建与获取共享内存

在这里插入图片描述
在这里插入图片描述
下面我们应该是要使用shmat和shmdt了,不过在此之前,我们还要介绍几个指令

2.shm相关指令

在这里插入图片描述
如何释放呢?
可以通过shmctl系统调用接口来释放,也可以通过指令来释放
我们先介绍指令释放
在这里插入图片描述
这里的key显示的是16进制,我们刚才打印的是10进制
因此我们改一下代码,让它以16进制打印
在这里插入图片描述

3.shmat和shmdt挂接和取消挂接

在这里插入图片描述
在这里插入图片描述

while :;do ipcs -m;sleep 1;done
这里反过滤掉了root创建的共享内存
while :;do ipcs -m | grep -v root;sleep 1;done

在这里插入图片描述
我们看到了挂接和取消挂接的全过程

4.shmctl获取共享内存信息,释放共享内存

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们实现了共享内存的创建/获取,挂接,取消挂接和释放
下面是时候开始让这两个进程开始通信了
在挂接之后,取消挂接之前开始通信

5.开始通信

我们刚才获取信息只是为了告诉大家这个函数有这么个功能而已,因此我们就不调用这个获取信息的函数了哦
在这里插入图片描述
在这里插入图片描述
通信成功
在这里插入图片描述
那么没有协同机制怎么办?
一个很好的方法是借助信号量来解决这一问题,但是因为信号量的接口太麻烦(比共享内存的这些接口还要麻烦很多),因此我们以后详细介绍信号量的时候再去使用信号量的接口

要不然是像我刚才那样通信双方约定好一个暗号,读端读到暗号时意味着通信结束
而是那样只能解决一部分情况下保证读端读取完所有的写端数据时才退出
还是无法解决写端还没写入你读端就开始读了啊

我们可以利用天然具有协同机制的管道啊!!
又因为我们这两个进程是没有血缘关系的,因此我们用一下命名管道吧
这里直接把我们之前写的管理命名管道的代码拿过来

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cerrno>
#include <cstring>
#include <string>
using namespace std;
const char* path="./namedpipe";
#define MODE 0666
class Fifo
{
public:
    Fifo(const char* path):_path(path)//用成员变量保存路径
    {
        int ret=mkfifo(_path.c_str(),MODE);
        if(ret==-1)//说明创建失败
        {
            cerr<<"create namedpipe fail, errno: "<<errno<<" , strerror: "<<strerror(errno)<<endl;
        }
        else
        {
            cout<<"create namedpipe succeed"<<endl;
        }
    }
    ~Fifo()
    {
        unlink(_path.c_str());
    }
private:
    string _path;
};

5.利用管道实现共享内存的协同机制

1.Sync(同步类)

在这里插入图片描述
在这里插入图片描述

2.读写端的修改

在这里插入图片描述
在这里插入图片描述

3.动图演示

在这里插入图片描述
成功解决了写端没写数据,读端还读的问题

6.共享内存的优缺点

在这里插入图片描述
下面我们趁热打铁快速了解一下消息队列

二.消息队列

1.概念

在这里插入图片描述
消息队列的生命周期也是随内核的,跟共享内存一样

2.接口,数据结构等等

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三.信号量理论

1.信号量的原理

这里介绍信号量的原理,
一方面是为了让我们更好地理解信号量,一方面是先提出一些多线程当中的概念

在这里插入图片描述

2.信号量的理论

1.从生活中的例子理解信号量

在这里插入图片描述

2.进程角度的信号量

在这里插入图片描述

3.信号量的细节

1.信号量必须要由OS提供并维护

在这里插入图片描述

2.信号量的基本操作

在这里插入图片描述

3.信号量的接口

1.semget

在这里插入图片描述

2.semctl

在这里插入图片描述

3.semop

在这里插入图片描述
关于信号量的更多话题我们等到多线程的时候还会再说明的

四.System V系列的进程间通信的小总结

共享内存,消息队列和信号量的很多接口都是相同的,它们的内核数据结构当中也都有一个一样的结构体:ipc_perm,
它们都是主要应用于本地通信的,因此在目前的网络时代当中并不常用(用的更多的还是网络通信)

它们都属于System V系列的进程间通信,OS为了管理它们搞了一个
ipc_ids结构体,ipc_id_ary结构体,kern_ipc_perm结构体实现了ipc资源的动态申请和释放,并将对ipc资源的管理转换为了对kern_ipc_perm的增删查改和对ipc_id_ary的动态扩容
在这里插入图片描述
在这里插入图片描述
不过因为System V系列的进程间通信的结构和数据结构都是独立设计的,跟文件是完全解耦的,因此不符合Linux一切皆文件的设计思想,这也是System V系列的进程间通信并不热门的原因

如果OS能够在struct file当中封装一个ipc_perm的指针,把kern_ipc_perm关联起来,并利用文件接口封装ipc资源使用的接口,就能让System V系列的进程间通信符合一切皆文件

那样的话使用起来肯定也就更容易,肯定就能热门了

五.利用信号量实现共享内存的协同机制

本来想写完前4点就结束吧,不过心血来潮想用一下信号量,下面我们一起来用一下信号量吧

1.思路

依旧是回归我们之前需要利用管道实现共享内存的协同机制的时候
我们的目的是让读端一开始阻塞等待,等到写端准备要进行写入的时候告诉读端: 我要开始写啦,你也开始读吧

此时就能够保证读端不会在一开始的时候做无意义的读取操作

大致流程分为:

  1. 读端(Server)创建并获取信号量
  2. Server阻塞申请信号量资源,此时读端就是阻塞等待写端进行写入
  3. Client获取读端创建好的信号量
  4. 写端(Client)准备写入时初始化信号量资源
  5. Server成功申请信号量资源,开始进程间通信
  6. 最后Server释放信号量资源

流程很清晰,
(那为什么我们一开始不用信号量呢? 因为信号量接口太麻烦了…,而且我用管道和信号量来解决这一共享内存的同步机制是为了学习熟悉这些接口)

之前有一些点我没有注意到,写代码的时候屡屡碰壁,最后才搞过来了

2.Server创建并获取信号量,Client获取信号量 -> ftok和semget

1.ftok

在这里插入图片描述

  1. ftok里面传入的路径必须是我们Linux系统中的确存在的路径!!!
  2. 我们申请的共享内存和信号量各自的key是不可以相同的(大家也能够很好的理解,因为key才是ipc资源的唯一标识嘛)

我们就用这个产生sem的key
在这里插入图片描述
用这个产生shm的key
在这里插入图片描述

2.shmget

在这里插入图片描述
我们共享内存的资源就是一个整体,因此nsems传入1
然后跟共享内存一样,Server传IPC_CREAT | IPC_EXCL | 0666, Client传入IPC_CREAT即可
在这里插入图片描述
Server:
在这里插入图片描述
Client:
在这里插入图片描述

3.Server阻塞申请信号量资源 - semop

在这里插入图片描述
当sem_op<0即需要申请信号量时,如果信号量==0,那么该进程就会阻塞,等待信号量>0
而信号量在还没有被进程设置之前默认值是0,因此我们可以这样来玩
(注意:

  1. semget时传入的nsems时你申请的这个信号量集当中的信号量数目,而不是信号量的初始值!!
  2. 初始值需要进程显式传入,而且默认值是0[我就是因为这点屡屡碰壁]
  3. sembuf是本来就有的,不需要我们显式提供[我就是因为这点屡屡碰壁]
    )
    Server不设置信号量,在读取之前申请信号量资源阻塞等待写端进行写入(我们起名为lock函数)
    Client即将进行写入之前初始化该信号量为1(我们起名为Unlock函数),此时Server等待成功,退出阻塞状态,开始进行读取操作
    在这里插入图片描述

4.Client初始化信号量资源 - semctl

在这里插入图片描述
在这里插入图片描述

5.Server释放信号量资源 - semctl

在这里插入图片描述

6.完整代码

1.Common.hpp

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
using namespace std;

const char* shm_path="/home/wzs/ubuntucode/process_ipc/semaphore";
const int shm_id=0x5678;
const int agreeSize=4096;

key_t GetKey(const char* k_path,int proj_id)
{
    key_t ret=ftok(k_path,proj_id);
    if(ret==-1)
    {
        cout<<"ftok fail"<<endl;
        exit(1);
    }
    return ret;
}

class Shm
{
public:
    int GetShmid(int key,int size,int shmflg)
    {
        int shmid=shmget(key,size,shmflg);
        if(shmid==-1)
        {
            cout<<"shmget fail"<<endl;
            exit(1);
        }
        return shmid;
    }

    void* Attach(int shmid)
    {
        void* addr=shmat(shmid,nullptr,0);
        if(addr==(void*)-1)
        {
            cout<<"shmat fail"<<endl;
            exit(1);
        }
        return addr;
    }

    void Detach(void* addr)
    {
        shmdt(addr);
    }

    void DelShm(int shmid)
    {
        shmctl(shmid,IPC_RMID,nullptr);
    }
};

2.sem.hpp

#pragma once
#include <sys/sem.h>
const char* sem_path="/home/wzs/ubuntucode/process_ipc/pipe/namepipe/name_pipepool";
const int sem_id=0x3f45289;

union semun {  
    int val;                /* 用于SETVAL */  
    struct semid_ds *buf;  /* 用于IPC_STAT和IPC_SET */  
    unsigned short *array; /* 用于GETALL和SETALL */  
};

void ChangeCount(sembuf* buf,int val)
{
    buf->sem_num=0;
    buf->sem_op=val;
    buf->sem_flg=SEM_UNDO;
}

class Sem
{
public:
    int GetSemid(key_t key,int nsems,int semflg)
    {
        int semid=semget(key,nsems,semflg);
        if(semid==-1)
        {
            cout<<"semget fail"<<endl;
            exit(1);
        }
        return semid;
    }

    void DelSem(int semid)
    {
        semctl(semid,0,IPC_RMID);
    }

    void Change(int semid,sembuf* sops)
    {
        cout<<"wait sem resource..."<<endl;
        semop(semid,sops,1);
        cout<<"wait success!"<<endl;
    }

    void GetInfo(int semid)
    {
        int val=semctl(semid,0,GETVAL);
        cout<<val<<endl;
    }

    void Init(int semid,semun un)
    {
        semctl(semid,0,SETVAL,un);
    }


    void Unlock(int semid)
    {
        union semun sem_union;
        sem_union.val=1;//将信号量的初始值设置为1,此时相当于开锁,读端可以拿到信号量,开始读取
        Init(semid,sem_union);
    }

    void lock(int semid)
    {
        sembuf buf;
        ChangeCount(&buf,-1);
        Change(semid,&buf);//我想申请信号量,但是信号量默认是0,我需要阻塞等待
    }
};

3.ShmServer.cpp

#include "Common.hpp"
#include "sem.hpp"
int main()
{
    Shm shm;
    key_t sem_key=GetKey(shm_path,shm_id);//获取Key
    int shmid=shm.GetShmid(sem_key,agreeSize,IPC_CREAT | IPC_EXCL | 0666);//申请shm
    char* addr=(char*)shm.Attach(shmid);//挂接shm

    //利用二元信号量(锁)
    Sem sem;
    key_t k=GetKey(sem_path,sem_id);

    int semid=sem.GetSemid(k,1,IPC_CREAT | IPC_EXCL | 0666);

    //等待获取锁
    sem.lock(semid);

    cout<<"receive message begin########################################"<<endl;
    //开始读取
    while(true)
    {
        if(addr[0]=='q') break;
        cout<<"this is message:"<<addr<<"。"<<endl;
        sleep(1);
    }
    cout<<"receive message over#########################################"<<endl;
    cout<<"Server will detach shm now..."<<endl;

    shm.Detach(addr);//解除挂接
    shm.DelShm(shmid);//删除shm
    sem.DelSem(semid);//删除sem
    return 0;
}

4.ShmClient.cpp

#include "Common.hpp"
#include "sem.hpp"
int main()
{
    Shm shm;
    key_t sem_key=GetKey(shm_path,shm_id);
    int shmid=shm.GetShmid(sem_key,agreeSize,IPC_CREAT);

    Sem sem;
    key_t k=GetKey(sem_path,sem_id);
    int semid=sem.GetSemid(k,1,IPC_CREAT);
    char* addr=(char*)shm.Attach(shmid);
    memset(addr,0,agreeSize);


    cout<<"send message begin########################################"<<endl;
    //开锁
    sem.Unlock(semid);

    for(char c='A';c<='Z';c++)
    {
        addr[c-'A']=c;
        sleep(1);
    }
    addr[0]='q';

    cout<<"send message over########################################"<<endl;
    cout<<"Client will detach shm now..."<<endl;

    shm.Detach(addr);
    return 0;
}

7.演示

在这里插入图片描述

以上就是Linux 进程间通信 System V系列: 共享内存,信号量,简单介绍消息队列的全部内容,希望能对大家有所帮助!!

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

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

相关文章

如何访问远程MySQL数据库服务器?

访问远程MySQL数据库服务器是一项常见的任务&#xff0c;它允许我们在不同的地点通过网络连接到MySQL服务器&#xff0c;并进行数据库管理和数据处理操作。我们将重点介绍一种名为【天联】的组网技术&#xff0c;该技术提供了一系列优势&#xff0c;使远程访问MySQL数据库服务器…

<网络安全>《83 微课堂<国家数据要素总体框架>》

1 总体框架 国家数据要素化总体框架由“六横两纵”共八个关键环节构成。 2 国家数据基础设施&#xff08;NDI&#xff09; 最底部第一层是国家数据基础设施&#xff08;NDI&#xff09;。国家数据基础设施&#xff08;NDI&#xff09;是经济社会进入数据要素化发展新阶段后新…

Github2024-05-10开日报 Top10

根据Github Trendings的统计&#xff0c;今日(2024-05-10统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目4TypeScript项目4JavaScript项目1Lua项目1C项目1Rust项目1Dart项目1 RustDesk: 用Rust编写的开源远…

【2024】Gradle安装配置以及基于Kotlin 进行详细使用

目录&#x1f4bb; 一、介绍二、安装Gradle1、下载安装2、配置环境变量2.1、mac2.2、windows 3、配置国内国内镜像源4、初始化Gradle项目4.1、项目结构4.2、Gradle常用命令 三、项目配置1、配置文件介绍1.1、设置文件settings.gradle1.1.1、单体项目1.1.2、父子项目 1.2、构建文…

Springboot+mybatis-plus+dynamic-datasource+继承DynamicRoutingDataSource切换数据源

Springbootmybatis-plusdynamic-datasource继承DynamicRoutingDataSource切换数据源 背景 最近公司要求支持saas&#xff0c;实现动态切换库的操作&#xff0c;默认会加载主租户的数据源&#xff0c;其他租户数据源在使用过程中自动创建加入。 解决问题 1.通过请求中设置租…

STC8增强型单片机开发【定时器Timer⭐】

目录 一、引言 二、定时器基础知识 三、STC8定时器配置 四、代码示例 五、总结 一、引言 在单片机开发中&#xff0c;定时器&#xff08;Timer&#xff09;是一个极其重要的组件&#xff0c;它允许开发者基于时间触发各种事件或任务。STC8增强型单片机作为一款功能丰富的…

【WPF学习笔记(一)】WPF应用程序的组成及Window类介绍

WPF应用程序的组成及Window类介绍 WPF应用程序的组成及Window类介绍前言正文1、WPF介绍1.1 什么是WPF1.2 WPF的特点1.3 WPF的控件分类 2、XAML介绍2.1 XAML的定义2.2 XAML的特点2.3 XAML的命名空间 3、WPF应用程序组成3.1 App.config3.2 App.xaml3.3 App.xaml.cs3.4 MainWindow…

六级仔细阅读

画两到三个词&#xff0c;精准定位 要原文和同义都满足才选 先看题目&#xff0c;在看原文&#xff0c;不要先看选项 做不出答案就继续往下读&#xff0c;读出来了就不用继续读了 分清楚是问为什么还是是什么&#xff0c;是什么看前面&#xff0c;为什么看后面 不知道就优先…

用例自动生成工具:Tcases实践指南

软件质量保障 所寫即所思&#xff5c;一个阿里质量人对测试的所感所悟。 Tcases能做什么? Tcases 是一个用于设计测试用例的工具。无论何种系统&#xff0c;无论何种测试类型&#xff08;单元测试、系统测试等&#xff09;&#xff0c;都可以使用 Tcases 来设计测试。你也可以…

【系统分析师】软件架构设计

文章目录 1、构件与软件复用1.1 主流构件标准1.2 构件获取与管理1.3 构件复用的方法 2、软件架构概述3、软件架构建模4、软件架构风格4.1 经典架构风格4.2 层次架构风格4.3 富互联网应用-RIA 5、面向服务的架构5.1 SOA概述5.2 SOA的关键技术5.3 SOA的实现方法 6、软件架构评估6…

使用python获取一下microsoft的搜索积分

主要使用的库是pyautogui PyAutoGUI接管了鼠标、键盘使用权,基本上完全照搬人的操作; 主要步骤如下: 登录edge浏览器打开搜索页面 找到搜索框的位置坐标使用pyautogui模拟点击搜索框模拟输入搜索文字模拟点击键盘enter键重复以上动作伪代码如下: import pyautogui import ti…

linux性能监控之top

说完了atop和htop&#xff0c;我们在来说说Linux自带的top&#xff0c;我们先看看命令效果&#xff1a; 可以看到是一个实时的系统监控工具&#xff0c;提供了一个动态的、交互式的实时视图&#xff0c;显示系统的整体性能信息以及正在运行的进程的相关信息。 我们先来解析下命…

【AI+换脸换装】从OpenAI 探索色情露骨内容领域浅聊AI换脸换装

5月9日消息&#xff0c;据外电报道&#xff0c;OpenAI 周三发布了文档草案&#xff0c;阐述了它希望 ChatGPT 及其其他人工智能技术如何运作。冗长的Model Spec 文件的一部分透露&#xff0c;该公司正在探索进军色情和其他露骨内容领域。 看完这个&#xff0c;心里有点惊讶&am…

Go实现树莓派I2C读取SHT30温度湿度传感器

简介 树莓派其实本身包含很多资源引脚&#xff0c; 合理利用其实可以自制智能家居的一部分&#xff0c;本身硬件和Linux系统等高级语言支持加生态&#xff0c; 不说了&#xff0c; 做就好了… I2C 功能开启 参考之前的文章就可以了 Go实现树莓派读取bh1750光照强度 查看I2C总…

大米自动化生产线揭秘:包装设备选择与维护之道

在现代化的大米生产过程中&#xff0c;自动化生产线的应用已经越来越广泛。其中&#xff0c;包装设备作为生产线上的重要一环&#xff0c;其选择与维护直接关系到产品的质量和生产效率。与星派一起探讨大米自动化生产线中包装设备的选择与维护之道。 一、包装设备的选择 在选择…

寻找最大价值的矿堆 - 矩阵

系列文章目录 文章目录 系列文章目录前言一、题目描述二、输入描述三、输出描述四、Java代码五、测试用例 前言 本人最近再练习算法&#xff0c;所以会发布一些解题思路&#xff0c;希望大家多指教 一、题目描述 给你一个由’0’(空地)、‘1’(银矿)、‘2’(金矿)组成的地图…

Element快速上手!

Element是饿了么公司前端团队开发的一套基于Vue的组件库&#xff0c;用于快速构建网页~ 官网链接&#xff1a; Element - The worlds most popular Vue UI frameworkElement&#xff0c;一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库https://element.elem…

未授权访问:Memcached 未授权访问漏洞

目录 1、漏洞原理 2、环境搭建 3、未授权访问 防御手段 今天继续学习各种未授权访问的知识和相关的实操实验&#xff0c;一共有好多篇&#xff0c;内容主要是参考先知社区的一位大佬的关于未授权访问的好文章&#xff0c;还有其他大佬总结好的文章&#xff1a; 这里附上大…

求阶乘n!末尾0的个数溢出了怎么办

小林最近遇到一个问题&#xff1a;“对于任意给定的一个正整数n&#xff0c;统计其阶乘n&#xff01;的末尾中0的个数”&#xff0c;这个问题究竟该如何解决&#xff1f; 先用n5来解决这个问题。n的阶乘即n!5!5*4*3*2*1120&#xff0c;显然应该为2个数相乘等于10才能得到一个结…

AI大模型探索之路-训练篇20:大语言模型预训练-常见微调技术对比

系列篇章&#x1f4a5; AI大模型探索之路-训练篇1&#xff1a;大语言模型微调基础认知 AI大模型探索之路-训练篇2&#xff1a;大语言模型预训练基础认知 AI大模型探索之路-训练篇3&#xff1a;大语言模型全景解读 AI大模型探索之路-训练篇4&#xff1a;大语言模型训练数据集概…