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

有在网上找做网站的人么/如何推广app让别人注册

有在网上找做网站的人么,如何推广app让别人注册,如何做Google外贸网站,wordpress前端表格插件总言 进程间通信:简述进程间通信,介绍一些通信方式,管道通信(匿名、名命)、共享内存等。 文章目录 总言1、进程间通信简述2、管道2.1、简介2.2、匿名管道2.2.1、匿名管道的原理2.2.2、编码理解:用fork来共…

总言

  进程间通信:简述进程间通信,介绍一些通信方式,管道通信(匿名、名命)、共享内存等。

文章目录

  • 总言
  • 1、进程间通信简述
  • 2、管道
    • 2.1、简介
    • 2.2、匿名管道
      • 2.2.1、匿名管道的原理
      • 2.2.2、编码理解:用fork来共享管道
        • 2.2.2.1、准备工作一:创建一个管道pipe
        • 2.2.2.2、准备工作二:创建子进程,对父子进程构建单向通信的信道
        • 2.2.2.3、正式工作:父子进程间通信
        • 2.2.2.4、结束工作:收尾处理(附整体代码)
      • 2.2.3、总结说明(匿名管道特点、读写原则)
      • 2.2.4、扩展应用:进程池的模拟实现
        • 2.2.4.1、processpool.c
        • 2.2.4.2、Task.hpp
    • 2.3、名命管道
      • 2.3.1、是什么
      • 2.3.2、相关使用演示
        • 2.3.2.1、comm.hpp
        • 2.3.2.2、log.hpp
        • 2.3.2.3、server.cxx
        • 2.3.2.4、client.cxx
        • 2.3.2.5、server端加入子进程(多个读端演示)
  • 3、system V 共享内存
    • 3.1、基础理论
    • 3.2、实操环节
      • 3.2.1、相关函数、指令汇总(通讯前后的工作)
        • 3.2.1.1、shmget
        • 3.2.1.2、ftok
        • 3.2.1.3、ipcs、ipcrm
        • 3.2.1.4、shmat、shmdt
        • 3.2.1.5、shmclt
      • 3.2.2、使用演示
        • 3.2.2.1、基本说明
        • 3.2.2.2、comm.hpp
        • 3.2.2.3、log.hpp
        • 3.2.2.4、shmserver.cxx
        • 3.2.2.5、shmclient.cxx
    • 3.3、总结补充
  • 4、system V 信号量
    • 4.1、理解层面

  
  
  

1、进程间通信简述

  1)、为什么要进行进程间通信?

  以下为一些举例:
  数据传输:一个进程需要将它的数据发送给另一个进程。
  资源共享:多个进程之间共享同样的资源。
  通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
  
  
  
  2)、要达成上述进程间通信,需要面临哪些问题,为什么?
  说明:进程具有独立性,要实现进程间通信,则需要让通信双方进程能够看到同一份"资源",此处强调"共享"这一特性。
  
  
  
  3)、进程间通信发展简述
在这里插入图片描述

  
  
  
  

2、管道

2.1、简介

  1)、什么是管道
  管道是Unix中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。
  
  特点:单向通信、传送的都是资源(数据是计算机里最重要的资源)
  
  说明:由于进程具有独立性,若让进程来提供管道,其它进程是看不到该管道存在的。因此,计算机里管道的实现,由操作系统提供,由进程来使用。
在这里插入图片描述

  
  
  

2.2、匿名管道

2.2.1、匿名管道的原理

  1)、问题引入:当进程打开一个文件后,创建一个子进程会发生什么?

在这里插入图片描述

  回答:对进程相关数据结构(*filefiles_structfile* fd_array等),子进程会进行写时拷贝;对被打开文件相关的内核数据结构(文件信息所生成的文件结构file),父子进程指向相同。由此诞生了共享文件,即管道。
  
  
  
  
  2)、如何实现一个管道?
  实际上,管道的底层原理就是通过文件实现的。相关步骤说明:
  1、分别以读写方式打开同一个文件
  2、fork()创建子进程(此时父子进程虽然彼此独立,但它们共享同一个文件)
  3、双方进程各自关闭自己不需要的文件描述符(假设父进程写入、子进程读取,则父进程关闭读端,子进程关闭写端。)
  
  PS:进程间通信属于内存级别的通信,大多以临时数据为主,不需要写入磁盘,虽然存在,但一般情况下没必要,且影响传送效率。
  
  

  
  
  

2.2.2、编码理解:用fork来共享管道

2.2.2.1、准备工作一:创建一个管道pipe

  1)、相关函数介绍
  这里我们使用pipe函数,其作用是创建一个匿名管道。头文件为<unistd.h>。(man 2 pipe查看具体信息。)

NAMEpipe, pipe2 - create pipeSYNOPSIS#include <unistd.h>int pipe(int pipefd[2]);

  
  说明:该函数用于获取两个输出型参数(这里返回文件描述符数组)。pipefd类似于基础IO中学习的文件描述标识符,这里pipefd[0]表示管道的读端,pipefd[1]表示管道的写端。注意参数的返回类型是int

 pipe()  creates  a  pipe, a unidirectional data channel that can be used for interprocess communica‐tion.  The array pipefd is used to return two file descriptors referring to the ends  of  the  pipe.pipefd[0]  refers to the read end of the pipe.  pipefd[1] refers to the write end of the pipe.  Datawritten to the write end of the pipe is buffered by the kernel until it is read from the read end ofthe pipe.  For further details, see pipe(7).

  
  返回值:int类型,若成功,则返回0,若失败则返回-1

RETURN VALUEOn success, zero is returned.  On error, -1 is returned, and errno is set appropriately.

  
  
  2)、相关使用如下

    //step1:创建一个管道int pipefd[2] = {0};//文件描述符数组,用于接受pipe读端、写端int ret = pipe(pipefd);assert(ret != -1);//用于检测是否成功创建管道(void)ret;

  说明:
  1、pipe(pipefd):这里我们并未给出管道名称,所以说pipe创建的是一个匿名管道。
  2、(void)retassert只在debug模式下有效,为了避免release模式下,ret变量因无效(未使用)而报警,此处设置(void)ret以证其使用性。
  
  
  
  3)、验证:pipefd表示的含义
在这里插入图片描述

  

    //验证:pipefd[2]具体含义cout << "pipefd[0]" << pipefd[0] << endl;cout << "pipefd[1]" << pipefd[1] << endl;
mypipe.out:mypipe.ccg++ -o $@ $^ -std=c++11 -DDEBUG //debug模式

  
  
  
  
  

2.2.2.2、准备工作二:创建子进程,对父子进程构建单向通信的信道

  说明:上述步骤,我们只是创建出管道,未曾实际使用它。此处我们以fork的形式创建父子进程,让其使用管道进行通信。根据之前描述的如何实现一个管道,相关示意图如下:
在这里插入图片描述

  
  相关代码如下:

    //step2:创建子进程,对父子进程构建单向通信的信道pid_t id = fork();assert(id != -1);//fork失败if(id = 1)//子进程:此处设子进程读取{close(pipefd[1]);//对子进程,保留读端,关闭写端//相关读取操作//……exit(0);}//父进程:此处设父进程写入close(pipefd[0]);//对父进程,保留写端,关闭读端//相关写入操作//……

  
  man 2 fork

SYNOPSIS#include <unistd.h>pid_t fork(void);RETURN VALUEOn  success, the PID of the child process is returned in the parent, and 0 is returned in the child.On failure, -1 is returned in the parent, no child process is created, and errno  is  set  appropri‐ately.

  
  

2.2.2.3、正式工作:父子进程间通信

  说明:在上述准备工作中,所创建的匿名管道能够让父子进程单向通信,只用在读端、写端分别编写相关代码操作即可。以下为一个演示案例(该部分内容不固定,仅做演示理解):

  对子进程,读取管道中的数据:使用read函数读取管道文件,并将读取到的数据存储在re_buffer中。

    if(0 == id){close(pipefd[1]);//子进程保留读端//……相关读取操作char re_buffer[1024*8]={0};//子进程缓冲区:用于接收读端读取到的数据while(true){//从管道中读取数据存储入缓冲区ssize_t re = read(pipefd[0], re_buffer, sizeof(re_buffer)-1);if(re > 0){re_buffer[re]= 0;cout << "【childpid-" << getpid() << ", get a message 】:" << re_buffer << endl;}else if(re == 0)//end of file,读取到0,文件结尾。{cout << "child: end of file, quit." << endl;break;//退出循环}}exit(0);//终止子进程}

  
  man 2 read
在这里插入图片描述
  
  
  对父进程,写入数据:这里设置写10次结束。

    //父进程close(pipefd[0]);//父进程保留写端//……相关写入操作char wr_buffer[1024*8]={0};//写端缓冲区,用于存储发送的数据string message = "父进程写入数据";//提示文字1int count=0;//提示文字2,用于记录写入次数while(true){//向wr_buffer中写入相关信息snprintf(wr_buffer,sizeof(wr_buffer),"fatherid-%d, %s, 第%d写入.",getpid(),message.c_str(),1+count++);//将写端缓冲区数据写入管道文件中write(pipefd[1],wr_buffer,strlen(wr_buffer));sleep(1);//便于观察输出结果而设//结束写端if(10 == count){cout<<"father quit!\n"<<endl;break;}}

  
  
  

2.2.2.4、结束工作:收尾处理(附整体代码)

  结束后,需要将通信管道关闭。

    //step3:执行完毕close(pipefd[1]);pid_t ret2 = waitpid(id, nullptr, 0);printf("wait:%d, childpid:%d, count:%d\n",ret2,id,count);assert(ret2 > 0);(void)ret2;

  演示结果:
在这里插入图片描述

  
  
  
  整体代码如下:

#include<iostream>
#include<unistd.h>
#include<assert.h>
#include<string>
#include<cstring>
#include<cstdio>
#include<sys/types.h>
#include<sys/wait.h>using namespace std;//演示以fork父子进程的方式创建匿名管道
int main()
{//step1:创建一个管道int pipefd[2] = {0};//文件描述符数组,用于接受pipe读端、写端int ret = pipe(pipefd);assert(ret != -1);//用于检测是否成功创建管道(void)ret;//assert只在debug模式下有效,为了避免release模式下ret变量未被使用而报警设置。//验证:pipefd[2]具体含义#ifdef DEBUGcout << "pipefd[0]" << pipefd[0] << endl;cout << "pipefd[1]" << pipefd[1] << endl;cout<<endl;#endif//step2:创建子进程,对父子进程构建单向通信的信道pid_t id = fork();assert(id != -1);//fork失败if(0 == id)//子进程:此处设子进程读取{close(pipefd[1]);//对子进程,保留读端,关闭写端//……相关读取操作char re_buffer[1024*8]={0};//子进程缓冲区:用于接收读端读取到的数据while(true){//从管道中读取数据存储入缓冲区ssize_t re = read(pipefd[0], re_buffer, sizeof(re_buffer)-1);if(re > 0){re_buffer[re]= 0;cout << "【childpid-" << getpid() << ", get a message 】:" << re_buffer << endl;}else if(re == 0)//end of file{cout << "child: end of file, quit." << endl;break;//退出循环}}exit(0);//终止子进程}//父进程:此处设父进程写入close(pipefd[0]);//对父进程,保留写端,关闭读端//……相关写入操作char wr_buffer[1024*8]={0};//写端缓冲区,用于存储发送的数据string message = "父进程写入数据";//提示文字1int count=0;//提示文字2while(true){//向wr_buffer中写入相关信息,并将其显示snprintf(wr_buffer,sizeof(wr_buffer),"fatherid-%d, %s, 第%d写入.",getpid(),message.c_str(),1+count++);//将写端缓冲区数据传入管道write(pipefd[1],wr_buffer,strlen(wr_buffer));//结束写端sleep(1);if(10 == count){cout<<"father quit!\n"<<endl;break;}}//step3:执行完毕close(pipefd[1]);pid_t ret2 = waitpid(id, nullptr, 0);printf("wait:%d, childpid:%d, count:%d\n",ret2,id,count);assert(ret2 > 0);(void)ret2;return 0;
}

  
  
  

2.2.3、总结说明(匿名管道特点、读写原则)

  1)、管道特点
  1、匿名管道只能用于具有亲缘关系的进程之间进行通信。通常运用于父子进程(一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。假如不断在fork,那么就能实现父子、兄弟之间共用同一管道)。

  2、管道通过让进程间协同,提供了访问控制。(根据之前fork的学习,创建子进程后,父子进程执行是没有先后顺序的,即缺乏访问控制)。

  3、管道提供流式服务。(即面向字节流,后续需要通过订制协议来进行区分)。

  4、管道是基于文件的,文件的生命周期随进程,故管道的生命周期是随进程的。(一般而言,进程退出,管道释放)。

  5、管道是半双工的,数据只能向一个方向流动。(需要双方通信时,需要建立起两个管道。)
  
  
  2)、管道读写原则
  两种访问方式和两种退出方式:
  1、若写端快,读端慢,管道写满了就不再写入;
  2、若写端慢,读端快,管道内没有数据时,读端必须等待;
  3、若写端关闭,读端最终会读取到0,标志到了文件结尾。
  4、若读端关闭,OS会终止写进行。
  
  
  
  

2.2.4、扩展应用:进程池的模拟实现

  上述匿名管道,可运用于创建进程池。如下图,当有多个子进程时,父子之间建立起管道通信。当父进程发送指令(command code),可派遣任务让随机一个子进程执行。
  在这里插入图片描述
  
  PS:该部分相关代码如下,可结合思维导图理解。相关演示结果如下:
在这里插入图片描述
在这里插入图片描述

  
  

2.2.4.1、processpool.c

#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include"task.hpp"#define PROCESS_NUM 5 //子进程数目
using namespace std;//子进程使用:用于等待接受父进程传递的任务编号指令,以便后续执行
int waitCommand(int pipefd, bool& quit)
{//读取:从对应的pipefd中读取到相关的指令,将其返回。uint32_t command = 0;int ret = read(pipefd, &command, sizeof(command));if(ret==0)//读取到文件末尾{quit=true;return -1;}assert(ret==sizeof(uint32_t));//用于检测读取到的指令是否合法return command;
}//父进程使用:用于委派任务给子进程
void sentAndWakeup(pid_t id, int pipefd, uint32_t command)
{//写入:将对应的command传递到子进程对应的通信管道中write(pipefd,&command,sizeof(command));cout<< "【father process-" << getpid() <<"】: call child process-" << id << ", execute task-" << desc[command] << ", child pipefd-" << pipefd <<"\n" <<endl;
}int main()
{//1、准备工作Load();//加载任务vector<pair<pid_t,int>> slots;//用于记录创建出的子进程,以及该进程对应的通信管道//2、创建子进程,建立单向通信管道for(int i = 0; i < PROCESS_NUM; ++i){//建立管道int pipefd[2];int n = pipe(pipefd);assert(n == 0);(void)n;//创建子进程pid_t id = fork();assert(id!=-1);if(0==id)//对子进程{close(pipefd[1]);//3、子进程开始工作while(true){// 等待父进程命令bool quit = false;int command = waitCommand(pipefd[0], quit);if(quit)//在管道中读取到文件尾break;// 执行对应的命令:此处对command做了检测if(command >= 0 && command <handlerSize()){calltasks[command];}else{cout<<"command error: "<< command << endl;}}exit(1);//子进程结束,退出}//2、对父进程close(pipefd[0]);//对管道:关闭读端slots.push_back({id,pipefd[1]});//对进程:记录当前创建的子进程id及其通信管道fd}//3、父进程开始工作:派遣任务srand((unsigned long)time(nullptr)*getpid());//随机数方式的负载均衡,这里为了尽量随机可做一些处理while(true){int command = rand() % handlerSize();// 选择一个任务:获取任务编号int choosechild = rand() % slots.size();// 选择一个子进程:获取到子进程id,通信管道fdsentAndWakeup(slots[choosechild].first, slots[choosechild].second, command); // 将任务派遣给子进程:根据选择的进程choosechild,将command任务编号传入该进程对应的通信管道。sleep(1);}//4、父进程结束工作:收尾处理for(const auto &it : slots)//关闭所有通信管道{close(it.second);}for(const auto &it : slots)//回收所有的子进程信息{waitpid(it.first,nullptr,0);}return 0;
}

  
  
  
  
  

2.2.4.2、Task.hpp
#pragma once
#include<iostream>
#include<vector>
#include<unordered_map>
#include<functional>
#include<unistd.h>using func = std::function<void()>;
std::vector<func> calltasks;//任务表:存储各函数接口std::unordered_map<int,std::string> desc;//任务编号及其文字描述//任务:(各函数实现)
void readMySQL()
{std::cout << "【child process-" << getpid() << " 】 :执行访问数据库的任务\n" << std::endl;
}void execuleUrl()
{std::cout << "【child process-" << getpid() << " 】 :执行url解析\n" << std::endl;
}void cal()
{std::cout << "【child process-" << getpid() << " 】 :执行加密任务\n" << std::endl;
}void save()
{std::cout << "【child process-" << getpid() << " 】 执行数据持久化任务\n" << std::endl;
}//任务汇总
void Load()
{desc.insert({calltasks.size(),"readMySQL:读取数据库"});calltasks.push_back(readMySQL);desc.insert({calltasks.size(),"execuleUrl:进行url解析"});calltasks.push_back(execuleUrl);desc.insert({calltasks.size(),"cal:进行加密"});calltasks.push_back(cal);desc.insert({calltasks.size(),"saveL:进行数据持久化"});calltasks.push_back(save);}void showHandler()//用于展示目前所具有的任务信息及其编号
{for(auto &it: desc){std::cout << it.first << "\t" << it.second << std::endl;}
}int handlerSize()//用于获取函数数组的大小,即任务总数
{return calltasks.size();
}

  
  
  
  
  

2.3、名命管道

2.3.1、是什么

  1)、问题引入:对于非血缘关系的进程,如何让二者看到同一份资源?
  
  回答:在磁盘上建立名命管道文件(FIFO文件),可以被双方进程打开,但不会将内存数据刷新到磁盘上。(PS:命名管道是一种特殊类型的文件,其在磁盘中大小为0)。
  
  
  
  2)、如何创建一份名命管道文件?
  相关函数指令:mkfifo,(man 3 mkfifo 可在Linux中查看)。
  

在这里插入图片描述函数介绍

  头文件和对应函数声明:

NAMEmkfifo - make a FIFO special file (a named pipe)SYNOPSIS#include <sys/types.h>#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);

  具体介绍:

DESCRIPTIONmkfifo()  makes  a FIFO special file with name pathname.  mode specifies the FIFO's permissions.It is modified by the process's umask in the usual way: the permissions of the created file  are(mode & ~umask).A FIFO special file is similar to a pipe, except that it is created in a different way.  Insteadof being an anonymous communications channel, a FIFO special file is entered into the file  sys‐tem by calling mkfifo().Once  you  have  created a FIFO special file in this way, any process can open it for reading orwriting, in the same way as an ordinary file.  However, it has to be open at both ends  simulta‐neously  before  you can proceed to do any input or output operations on it.  Opening a FIFO forreading normally blocks until some other process opens the  same  FIFO  for  writing,  and  viceversa.  See fifo(7) for nonblocking handling of FIFO special files.

  返回值:

RETURN VALUEOn success mkfifo() returns 0.  In the case of an error, -1 is returned (in which case, errno isset appropriately).

  
  
  

在这里插入图片描述使用演示

  mkfifo named_pipe:mkfifo可在命令行上使用。

在这里插入图片描述

  

[wj@VM-4-3-centos T0917]$ mkfifo named_pipe
[wj@VM-4-3-centos T0917]$ ll
total 0
prw-rw-r-- 1 wj wj 0 Sep 16 20:48 named_pipe //可以看到其在磁盘上大小为0
[wj@VM-4-3-centos T0917]$ echo "hello" > named_pipe [wj@VM-4-3-centos T0917]$ cat < named_pipe 
hello

  
  以脚本演示:while :; do echo "hello pipe"; sleep 1 ; done > named_pipe
在这里插入图片描述
  
  
  
  
  

2.3.2、相关使用演示

  整体实现框架如下:
在这里插入图片描述
  演示结果:

在这里插入图片描述
  
  

2.3.2.1、comm.hpp
#ifndef _COMM_H_
#define _COMM_H_#include<iostream>
#include<string>
#include<cstring>
#include<assert.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include "log.hpp"using namespace std;#define MODE 0666 //mkfifo中第二参数mode
#define SIZE 1024 //读写端缓冲区大小//命名管道路径、名称:此处创建在当前路径下
string ipcPath="./fifo.ipc";#endif

  
  
  
  

2.3.2.2、log.hpp
#ifndef _LOG_H
#define _LOG_H#include<iostream>
#include<ctime>#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3const std::string msg[]={"Debug","Notice","Warning","Error"
};std::ostream &Log(std::string masseage, int label)
{std::cout << "[" << (unsigned long)time(nullptr) << "] :" << msg[label]<< ", "<< masseage << std::endl;return std::cout;
}#endif

  
  
  
  

2.3.2.3、server.cxx

#include "comm.hpp"static void getMessage(int fd)
{char buffer[SIZE]={0};//读端缓冲区while(true){//void * memset ( void * ptr, int value, size_t num );memset(buffer,'\0',sizeof(buffer));//为了每次接受信息前,清屏处理上此遗留的无效数据int rd = read(fd, buffer, sizeof(buffer) - 1);if (rd > 0) // 读取到数据{cout << "masseage from client:> " << buffer << endl;}else if (rd == 0) // 读取到文件尾end of file{cout << "end of the file, quit!" << endl;break;}else{perror("read"); // errorbreak;}}
}int main()//server:读取端,用于接受client发送的通信
{//1、创建管道文件int ret = mkfifo(ipcPath.c_str(), MODE);assert(0 == ret);(void)ret;Log("server:创建管道文件",Debug) << endl;//2、正常文件操作:接受通信、显示通信//2.1、打开名命管道int fd = open(ipcPath.c_str(), O_RDONLY);if(fd < 0){perror("open");exit(2);//处理assert,上述mkfifo也可以用perror检测}Log("server:打开名命管道",Debug)<< endl;//2.2、进行通信getMessage(fd);//2.3、通信结束,关闭文件close(fd);Log("server:通信结束,关闭文件",Debug)<< endl;//3、删除管道文件unlink(ipcPath.c_str());Log("server:删除管道文件",Debug)<< endl;return 0;
}

  
  
  
  

2.3.2.4、client.cxx

#include "comm.hpp"int main()//client:写入端,用于发送通信到管道中
{//1、获取管道文件int fd = open(ipcPath.c_str(), O_WRONLY);if(fd < 0){perror("open");exit(1);}Log("client: 获取管道文件",Debug)<< endl;//2、ipc通信过程string buffer;while(true){cout<< "client sent massage:> ";std::getline(std::cin, buffer);//获取数据到缓冲区write(fd,buffer.c_str(),buffer.size());//将缓冲区数据写入通信管道中}//3、关闭管道文件close(fd);Log("client: 关闭管道文件",Debug)<< endl;return 0;
}

  
  
  

2.3.2.5、server端加入子进程(多个读端演示)

  相关演示结果:
在这里插入图片描述
  
  只需要在server端做出小改动就行:

static void getMessage(int fd)
{char buffer[SIZE]={0};//读端缓冲区while(true){//void * memset ( void * ptr, int value, size_t num );memset(buffer,'\0',sizeof(buffer));//为了每次接受信息前,清屏处理上此遗留的无效数据int rd = read(fd, buffer, sizeof(buffer) - 1);if (rd > 0) // 读取到数据{cout <<"child: " << getpid() <<" | masseage from client:> " << buffer << endl;}else if (rd == 0) // 读取到文件尾end of file{cout << "end of the file, quit!" << endl;break;}else{perror("read"); // errorbreak;}}
}
    // //2.2、进行通信// getMessage(fd);//2.2、写法二:创建子进程,进行多项读端通信int nums=3;//进程数目for(int i = 0; i < nums; ++i){int id = fork();assert(id >= 0);if(id == 0)//子进程:执行读取任务{getMessage(fd);exit(1);//子进程结束,退出进程}}for(int i = 0; i < nums; ++i)//父进程:等待子进程结束{waitpid(-1,nullptr,0);}

  
  
  
  
  
  
  

3、system V 共享内存

3.1、基础理论

  1)、共享内存的原理简述
在这里插入图片描述

  问题1、共享内存是谁提供的?
  回答:操作系统。共享内存是OS专门为了进程通信而设定的,进程只是使用它,而非拥有者。
  
  
  问题2、操作系统要不要管理共享内存?
  回答:需要。实际上共享内存=共享内存块+对应共享内存的内核数据结构。(PS:对于管道文件,OS也要管理,只不过其包含在文件管理中,不需要单独拎出处理)。
  
  
  

3.2、实操环节

3.2.1、相关函数、指令汇总(通讯前后的工作)

在这里插入图片描述

3.2.1.1、shmget

  1)、基础介绍
  shmget功能:用来创建共享内存。查看方式:man shmget

NAMEshmget - allocates a System V shared memory segmentSYNOPSIS#include <sys/ipc.h>#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);

  参数介绍:
  key:key值用于保证通信双方看到的都是同一个共享内存。通信双方(client&server)能够匹配同一个key值,则能看到同一个共享内存。其在OS中具有唯一性,其数值本身不重要。
  使用方法:使用同样的算法规则,使得通信双方获取同一个key值(唯一)。比如:ftok函数(下述介绍)。
  
  size:共享内存大小。
  size = 4096/4097:共享内存的大小最好是页(PAGE,4096,4kb)的整数倍。(PS:4097会创建两个页,但OS实际只给4097个bit)
  
  
  shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的。
在这里插入图片描述

       The value shmflg is composed of:IPC_CREAT   to create a new segment.  If this flag is not used, then shmget() will find the segment  asso‐ciated with key and check to see if the user has permission to access the segment.IPC_EXCL    used with IPC_CREAT to ensure failure if the segment already exists.

  
  返回值: 若创建成功,shmget的返回值为共享内存的用户层标识符,类似fd文件标识符。

RETURN VALUEOn success, a valid shared memory identifier is returned.  On errir, -1 is returned, and errno is  set  toindicate the error.

  相关使用举例见下述ipcs、ipcrm小节。
  
  
  
  

3.2.1.2、ftok

  1)、基础介绍
  功能:用于创建共享内存时,形成相同的key值。查看方式,man ftok

NAMEftok - convert a pathname and a project identifier to a System V IPC keySYNOPSIS#include <sys/types.h>#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);

  

DESCRIPTIONThe  ftok()  function  uses  the  identity of the file named by the given pathname (which must refer to anexisting, accessible file) and the least significant 8 bits of proj_id (which must be nonzero) to generatea key_t type System V IPC key, suitable for use with msgget(2), semget(2), or shmget(2).The  resulting value is the same for all pathnames that name the same file, when the same value of proj_idis used.  The value returned should be different when the (simultaneously existing) files or  the  projectIDs differ.

  
  返回值:

RETURN VALUEOn  success,  the generated key_t value is returned.  On failure -1 is returned, with errno indicating theerror as for the stat(2) system call.

  
  
  2)、使用演示

在这里插入图片描述

  
  相关代码:

[wj@VM-4-3-centos shm]$ ls
comm.hpp  log.hpp  makefile  shmclient.cxx  shmserver.cxx
[wj@VM-4-3-centos shm]$ make
g++ -o shmclient.out shmclient.cxx -std=c++11
g++ -o shmserver.out shmserver.cxx -std=c++11[wj@VM-4-3-centos shm]$ ls
comm.hpp  log.hpp  makefile  shmclient.cxx  shmclient.out  shmserver.cxx  shmserver.out[wj@VM-4-3-centos shm]$ ./shmserver.out 
[1694951640] :Debug, server:ftok successserver key: 1140916349[wj@VM-4-3-centos shm]$ ./shmclient.out 
[1694951646] :Debug, client:ftok successclinet key: 1140916349
[wj@VM-4-3-centos shm]$ 

  
  
  
  

3.2.1.3、ipcs、ipcrm

  1)、基础介绍
  ipcs

NAMEipcs - provide information on IPC facilitiesDESCRIPTIONipcs  provides information on the inter-process communication facilities for which the calling process hasread access.

  ipcs -m:可用于查看共享内存。

       -m, --shmemsWrite information about active shared memory segments.

  
  
  ipcrm

NAMEipcrm - remove a message queue, semaphore set or shared memory idSYNOPSISipcrm [options]

  ipcrm -m shmid:删除共享内存(注意此处需要填shmid值)

OPTIONS-M, --shmem-key shmkeyremoves the shared memorysegment created with shmkey after the last detach is performed.-m, --shmem-id shmidremoves the shared memory segment identified by shmid after the last detach is performed.

  
  
  
  2)、使用演示
  int shmget(key_t key, size_t size, int shmflg); shmlg带上权限时:

在这里插入图片描述

  使用ipcs 查看时各项含义简介:
在这里插入图片描述

  若使用shmget,参数shmlg中不加权限,结果如下:
在这里插入图片描述

  
  
  
  

3.2.1.4、shmat、shmdt

  1)、基础介绍
  man shmatman shmdt,可查看相关信息:
  shmat将共享内存段连接到进程地址空间;shmdt:将共享内存段与当前进程脱离。

NAMEshmat, shmdt - System V shared memory operationsSYNOPSIS#include <sys/types.h>#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg);int shmdt(const void *shmaddr);

  相关参数:
   shmid:共享内存标识
   shmaddr:指定连接的虚拟地址(一般设置为nullptr即可,由操作系统自行配置)
   shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
  
  返回值: 对shmat,成功返回一个指针,指向共享内存第一个节;失败返回-1。对shmdt,成功返回0,失败返回-1。

RETURN VALUEOn success shmat() returns the address of the attached shared memory  segment;  on  error  (void *) -1  isreturned, and errno is set to indicate the cause of the error.On  success  shmdt()  returns  0;  on  error -1 is returned, and errno is set to indicate the cause of theerror.

  相关使用演示见下述shmclt.
  
  
  
  
  

3.2.1.5、shmclt

  1)、基础介绍
  man shmclt,可查看相关信息:

NAMEshmctl - System V shared memory controlSYNOPSIS#include <sys/ipc.h>#include <sys/shm.h>int shmctl(int shmid, int cmd, struct shmid_ds *buf);DESCRIPTIONshmctl() performs the control operation specified by cmd on the System V shared memory segment whose iden‐tifier is given in shmid.The buf argument is a pointer to a shmid_ds structure,

  相关参数:
  cmd:该参数可针对shmid对应的共享内存进行各项选项操作(下述演示中只使用到IPC_RMID:即便是有进程和当下的shm挂接,依旧删除共享内存。)
  buf:为共享内存属性而准备,若不使用可设置为空nullptr。若想自行设置或做其它处理,可使用到该参数。
  
  返回值: 成功返回0;失败返回-1。

		Other operations return 0 on success.On error, -1 is returned, and errno is set appropriately.

  
  2)、使用演示
  用于观察的脚本:while :; do ipcs -m; sleep 1; done

在这里插入图片描述

  
  
  
  

3.2.2、使用演示

3.2.2.1、基本说明

  此部分整体介绍框架如下:
在这里插入图片描述
  
  须知:
  1、共享空间创建在虚拟地址中的共享区,该部分属于用户空间,不用经过系统调用就可以直接访问。即:通讯进程双方可以直接进行内核级别的读写。
  2、若创建共享内存后,写端没有写入任何消息数据,不妨碍读端读取数据(读取为空,共享内存被创建后默认清除所有数据)。
  
  使用时注意事项:
  1、使用共享内存,通信双方只要一方直接向共享内存中写入数据,另一方就可以立马看到对方写入的数据。
  2、共享内存缺乏访问控制!会带来并发问题。(如:写端数据信息尚未完全录入,读端就把数据读取出去。由于数据的不完整性,导致该数据无效或错误。)
  
  
  PS:实际进程间通信部分可以根据自己的需求设置,这里主要演示使用共享内存通讯时如何让其一定程度上得到访问控制。以下为整体代码展示。
在这里插入图片描述

  
  
  

3.2.2.2、comm.hpp
#pragma once#include<iostream>
#include<string>
#include<cstring>
#include<assert.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/stat.h>
#include<fcntl.h>
#include "log.hpp"using namespace std;#define SHM_SIZE 4096 //shmget参数:定义shm的大小,最好是页(PAGE,4096,4kb)的整数倍
#define PROJ_ID 0x44//ftok参数:值可随意,the least significant 8 bits of proj_id (which must be nonzero)
#define PATH_NAME "/home/wj"//路径正确即可。/
//用于让共享内存达到访问控制的一些设置:借助管道实现#define FIFO_NAME "./fifo" //将名命管道创建在当前目录下,名称为fifo#define READ O_RDONLY //以读的方式打开管道文件
#define WRITE O_WRONLY //以写的方式打开管道文件//类:用于让进程运行、关闭时,自动创建、销毁管道文件
class Init
{
public:Init()//默认构造{umask(0);int fifoid = mkfifo(FIFO_NAME,0666);assert(fifoid != -1);(void)fifoid;Log("FIFO pipe created!", Notice);}~Init()//默认析构{unlink(FIFO_NAME);Log("remove FIFO pipe!", Notice);}
};//函数:主要目的是为读写两端以不同方式打开管道文件
int OpenFIFO(std::string pathname, int flags)
{int fd = open(pathname.c_str(),flags);assert(fd > 0);(void)fd;return fd;
}//函数:等待。让读端处于等待中,直到从管道文件中接受到指令。
void Wait(int fd)//此处fd是管道文件
{uint32_t command = 0;//指令:对于输入内容不重要,只是为了控制ssize_t n = read(fd, &command, sizeof(uint32_t));assert(n == sizeof(uint32_t));(void)n;
}//函数:唤醒。当写端数据输入完成后,输入指令唤醒读端。
void Signal(int fd)//此处fd是管道文件
{uint32_t command = 0;ssize_t n = write(fd, &command, sizeof(uint32_t));assert(n == sizeof(uint32_t));(void)n;
}//函数:关闭管道文件.
void closeFIFO(int fd)
{close(fd);
}
/

  
  
  
  

3.2.2.3、log.hpp
#ifndef _LOG_H
#define _LOG_H#include<iostream>
#include<ctime>#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3const std::string msg[]={"Debug","Notice","Warning","Error"
};std::ostream &Log(std::string masseage, int label)
{std::cout << "[" << (unsigned long)time(nullptr) << "] :" << msg[label]<< ", "<< masseage << std::endl;return std::cout;
}#endif

  
  
  
  

3.2.2.4、shmserver.cxx

#include "comm.hpp"int main()
{// 1、创建公共的key值key_t k = ftok(PATH_NAME, PROJ_ID);if (k < 0){Log("client:ftok failure!", Error)<< " clinet key: " << k << endl;exit(1);}Log("client:ftok success", Debug) << " clinet key: " << k << endl;//2、获取共享内存int shmid = shmget(k, SHM_SIZE, IPC_CREAT);//若创建的共享内存在底层存在,则获取并返回if( shmid < 0){Log("client: get shm failure!", Error) << " the shmid:" << shmid << endl;exit(2);}Log("client: get shm success", Debug) << " the shmid:" << shmid << endl;//3、建立映射(关联)char* shmaddr = (char*)shmat(shmid, nullptr, 0);Log("client: attach shm success", Debug) << " the shmaddr: "<< shmaddr << endl;//4、进行通信:写端,输入数据//4.1、以写的方式打开管道文件int fd = OpenFIFO(FIFO_NAME,WRITE);//4.2、通讯:将数据写入共享内存while(true){int n = read(0, shmaddr, SHM_SIZE-1);if( n > 0){shmaddr[n-1] = 0;//键盘读取数据输入共享内存,最后一位为\n//4.3、写端:向名命管道中发送指令(内容随机),告知读端数据输入完毕Signal(fd);Log("写端:数据输入完成:>", Notice);if(strcmp(shmaddr,"quit")==0) break; //用于结束写端}}//4.3、完成通讯,关闭名命管道(见下述)//5、取消映射(去关联)int ret = shmdt(shmaddr);assert( ret != -1);Log("client: detach shm success", Debug) << " the ret: " << ret << endl;//6、server中做了处理,此处不用删除共享内存closeFIFO(fd);//关闭名命管道return 0;
}

  
  
  
  

3.2.2.5、shmclient.cxx

#include "comm.hpp"int main()
{// 1、创建公共的key值key_t k = ftok(PATH_NAME, PROJ_ID);if (k < 0){Log("client:ftok failure!", Error)<< " clinet key: " << k << endl;exit(1);}Log("client:ftok success", Debug) << " clinet key: " << k << endl;//2、获取共享内存int shmid = shmget(k, SHM_SIZE, IPC_CREAT);//若创建的共享内存在底层存在,则获取并返回if( shmid < 0){Log("client: get shm failure!", Error) << " the shmid:" << shmid << endl;exit(2);}Log("client: get shm success", Debug) << " the shmid:" << shmid << endl;//3、建立映射(关联)char* shmaddr = (char*)shmat(shmid, nullptr, 0);Log("client: attach shm success", Debug) << " the shmaddr: "<< shmaddr << endl;//4、进行通信:写端,输入数据//4.1、以写的方式打开管道文件int fd = OpenFIFO(FIFO_NAME,WRITE);//4.2、通讯:将数据写入共享内存while(true){int n = read(0, shmaddr, SHM_SIZE-1);if( n > 0){shmaddr[n-1] = 0;//键盘读取数据输入共享内存,最后一位为\n//4.3、写端:向名命管道中发送指令(内容随机),告知读端数据输入完毕Signal(fd);Log("写端:数据输入完成:>", Notice);if(strcmp(shmaddr,"quit")==0) break; //用于结束写端}}//4.3、完成通讯,关闭名命管道(见下述)//5、取消映射(去关联)int ret = shmdt(shmaddr);assert( ret != -1);Log("client: detach shm success", Debug) << " the ret: " << ret << endl;//6、server中做了处理,此处不用删除共享内存closeFIFO(fd);//关闭名命管道return 0;
}

  
  
  
  

3.3、总结补充

  
  1、我们把多个进程(执行流)看到的同一份公共资源,称之为临界资源。
  2、进程中,用于访问临界资源的代码称之为临界区。
  
  根据上述共享内存学习,当多个执行流不加保护地访问相同的临界资源时,会存在运行时互相干扰的情况,带来时序问题。
  
  3、为了更好的进行临界区的保护,可以让多个执行流在任何时刻,都只有一个进程进入临界区。将这种操作称之为互斥。
  
  4、原子性。
  
  
  
  

4、system V 信号量

4.1、理解层面

  
  为了合理保护和使用临界资源,当进程想要访问临界资源时,需要先申请信号量。信号量本质是一种计数器,当进程申请信号量成功,计数器 -1,临界资源内部会给进程预留下空间。进程执行自己的临界区代码,访问临界资源,结束后释放信号量,进而计数器+1。
  
  如果某一项操作只有一行汇编,该操作就是原子的。
   信号量计数器是对临界资源的预定机制,要保证其本身也是原子的。那么申请信号量时,计数器--(P操作),释放信号量,计数器++(V操作),二者都必须是原子的。
  
  
  问题:能否让多个进程看到同一个全局变量,将其作为信号量?
  回答:不可以。根据之前所学,CPU在执行进程时有调度,因执行流及其上下文数据的切换,此时会造成该临时变量具有时序问题。
  
  
  
  
  
  
  
  
  

相关文章:

【ONE·Linux || 进程间通信】

总言 进程间通信&#xff1a;简述进程间通信&#xff0c;介绍一些通信方式&#xff0c;管道通信&#xff08;匿名、名命&#xff09;、共享内存等。 文章目录 总言1、进程间通信简述2、管道2.1、简介2.2、匿名管道2.2.1、匿名管道的原理2.2.2、编码理解&#xff1a;用fork来共…...

207.Flink(二):架构及核心概念,flink从各种数据源读取数据,各种算子转化数据,将数据推送到各数据源

一、Flink架构及核心概念 1.系统架构 JobMaster是JobManager中最核心的组件,负责处理单独的作业(Job)。一个job对应一个jobManager 2.并行度 (1)并行度(Parallelism)概念 一个特定算子的子任务(subtask)的个数被称之为其并行度(parallelism)。这样,包含并行子任…...

debian终端快捷键设置

为了方便使用图形化debian&#xff0c;快捷调出shell终端是提升工作学习效率的最重要的一步。 1.首先点击右上角&#xff0c;选择设置 2.点击键盘&#xff0c;选择快捷键&#xff0c;并创建自定义快捷键 3.点击添加快捷键 4.根据图中提示创建快捷键 Name: Terminal Command…...

原生ajax

什么是Ajax Asynchronous JavaScript and xml 异步的 js 和 xml(数据承载方式) &#xff0c;本质&#xff1a;使用js提供的异步对象XMLHttpRequest 异步的向服务器提交请求&#xff0c;并且接受服务器响应回来的数据。 使用ajax 1.创建异步对象 var xhrnew XMLHttp…...

面试题库(五):并发编程

多线程类的使用 java线程同步有哪些方法、各自的优缺点synchronized 和ReentrantLock区别,可重入锁是什么?threadlocal有什么用Java中创建线程有几种方式?分别是? 当主线程执行结束后,子线程还会继续执行下去吗?JUC中有哪些常用的集合?(项目中用到的)CopyOnWriteArray…...

Android FileProvider笔记

一、FileProvider是什么 通过FileProvider.getUriForFile(NonNull Context context, NonNull String authority, NonNull File file)方法获得一个有临时权限的Uri给客户端用来访问本APP文件。 当然看FileProvider类的注释更加详细 二、代码示例 <providerandroid:name&q…...

华为云云耀云服务器L实例评测 |云服务器选购

华为云耀云服务器 L 实例是一款轻量级云服务器&#xff0c;开通选择实例即可立刻使用&#xff0c;不需要用户再对服务器进行基础配置。新用户还有专享优惠&#xff0c;2 核心 2G 内存 3M 带宽的服务器只要 89 元/年&#xff0c;可以点击华为云云耀云服务器 L 实例购买地址去购买…...

2023-09-22 LeetCode每日一题(将钱分给最多的儿童)

2023-09-22每日一题 一、题目编号 2591. 将钱分给最多的儿童二、题目链接 点击跳转到题目位置 三、题目描述 给你一个整数 money &#xff0c;表示你总共有的钱数&#xff08;单位为美元&#xff09;和另一个整数 children &#xff0c;表示你要将钱分配给多少个儿童。 你…...

功能测试的重要性

前言 在软件开发领域&#xff0c;功能测试是确保软件质量的关键步骤之一。正如其名称所示&#xff0c;功能测试是验证软件产品是否具有其描述的功能和符合预期结果的过程。这种类型的测试非常重要&#xff0c;因为它不仅可以帮助团队检测潜在的缺陷并提高软件品质&#xff0c;…...

《Linux高性能服务器编程》--高级I/O函数

目录 1--Pipe() 2--dup() 和 dup2() 3--readv() 和 writev() 4--sendfile() 5--mmap() 和 munmap() 6--spice() 7--tea() 8--fcntl() 1--Pipe() #include <unistd.h> int pipe(int fd[2]); // 成功返回0&#xff0c;失败返回-1 pipe() 函数可用于创建一个管道&a…...

算法通关村 | 透彻理解动态规划

1. 斐波那契数列 1&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;5&#xff0c;8&#xff0c;13&#xff0c;..... f(n) f(n-1) f(n-2) 代码实现 public static int count_2 0;public int fibonacci(int n){if (n < 2){count_2;return n;}int f1 1;int f2 2;i…...

数据结构(持续更新)

嗯,怎么说数据结构果然很玄妙。按照能不能存储多行元素大致分为两类。 不能存好几行的数据包括pair,int,float,double,char,struct; 能存好几行的:map,unordered_map,list,vector,set,string,array。 1. pair “pair” 是 C++ 标准库中的一个模板类,它用于存储…...

nginx部署vue后显示500 Internal Server Error解决方案

前言 描述&#xff1a;当我配置好全部之后&#xff0c;通过 服务器 ip 地址访问&#xff0c;遇到报错信息&#xff1a;500 Internal Server Error。 今天部署vue前端项目一直报错500&#xff0c;无法显示出主页面。 一个以为是自己的dist位置没有访问正确或者nginx.conf的位…...

微调大型语言模型(一):为什么要微调(Why finetune)?

今天我们来学习Deeplearning.ai的在线课程 微调大型语言模型(一)的第一课&#xff1a;为什么要微调(Why finetune)。 我们知道像GPT-3.5这样的大型语言模型(LLM)它所学到的知识截止到2021年9月&#xff0c;那么如果我们向ChatGPT询问2022年以后发生的事情&#xff0c;它可能会…...

【GO】网络请求例子

post请求;multipart/form-data类型 // 构建请求参数requestData : map[string]interface{}{"gb": "","code": "","reMemberInfo": map[string]interface{}{"shi": "","…...

泡泡玛特海外布局动作不断,开启东南亚潮玩盛会

近日&#xff0c;泡泡玛特海外布局动作不断&#xff0c;9月8日至10日&#xff0c;泡泡玛特2023 PTS潮流玩具展&#xff08;下简称新加坡PTS&#xff09;在新加坡滨海湾金沙成功举办&#xff0c;现场人气爆棚&#xff0c;三天吸引了超过2万观众入场&#xff0c;这也是泡泡玛特首…...

uniappAndroid平台签名证书(.keystore)生成

一、安装JRE环境 https://www.oracle.com/java/technologies/downloads/#java8 记住下载默认安装地址。ps&#xff1a;我都默认安装地址C:\Program Files\Java\jdk-1.8 二、安装成功后配置环境变量 系统变量配置 AVA_HOME 放到环境变量去 %JAVA_HOME%\bin 三、生成签名证书…...

Gateway学习和源码解析

文章目录 什么是网关&#xff1f;搭建实验项目demo-servicegateway-service尝试简单上手 路由&#xff08;Route&#xff09;断言&#xff08;Predicate&#xff09;和断言工厂&#xff08;Predicate Factory&#xff09;gateway自带的断言工厂After&#xff08;请求必须在某个…...

移动机器人运动规划 --- 基于图搜索的Dijkstra算法

移动机器人运动规划 --- 基于图搜索的Dijkstra算法 Dijkstra 算法Dijkstra 算法 伪代码流程Dijkstra 算法步骤示例Dijkstra算法的优劣分析 Dijkstra 算法 Dijkstra 算法与BFS算法的区别就是 : 从容器中弹出接下来要访问的节点的规则不同 BFS 弹出: 层级最浅的原则&#xff0c…...

Mybatis SQL构建器

上一篇我们介绍了在Mybatis映射器中使用SelectProvider、InsertProvider、UpdateProvider、DeleteProvider进行对数据的增删改查操作&#xff1b;本篇我们介绍如何使用SQL构建器在Provider中优雅的构建SQL语句。 如果您对在Mybatis映射器中使用SelectProvider、InsertProvider…...

怎么将几张图片做成pdf合在一起

怎么将几张图片做成pdf合在一起&#xff1f;在我们平时的工作中&#xff0c;图片和pdf都是非常重要的电脑文件&#xff0c;使用也非常频繁&#xff0c;图片能够更为直观的展示内容&#xff0c;而pdf则更加的正规&#xff0c;很多重要文件大多会做成pdf格式的。在职场人的日常工…...

关于JPA +SpringBoot 遇到的一些问题及解决方法

关于JPA SpringBoot 遇到的一些问题及解决方法&#xff08;可能会有你正在遇到的&#xff09; 一、JpaRepository相关 1.1 org.springframework.dao.InvalidDataAccessResourceUsageException: Named parameter not bound : id; nested exception is org.hibernate.QueryEx…...

​全国馆藏《乡村振兴战略下传统村落文化旅游设计》许少辉八一著作——2023学生开学季辉少许

​全国馆藏《乡村振兴战略下传统村落文化旅游设计》许少辉八一著作——2023学生开学季辉少许...

linux升级glibc-2.28

1.准备工作 1.1升级gcc到gcc8 # 安装devtoolset-8-gcc yum install centos-release-scl yum install devtoolset-8 scl enable devtoolset-8 -- bash# 启用工具 source /opt/rh/devtoolset-8/enable # 安装GCC-8 yum install -y devtoolset-8-gcc devtoolset-8-gcc-c devtoolse…...

[Go疑难杂症]为什么nil不等于nil

现象 在日常开发中&#xff0c;可能一不小心就会掉进 Go 语言的某些陷阱里&#xff0c;而本文要介绍的 nil ≠ nil 问题&#xff0c;便是其中一个&#xff0c;初看起来会让人觉得很诡异&#xff0c;摸不着头脑。 先来看个例子&#xff1a; type CustomizedError struct {Err…...

C#60个常见的问题和答案

在本文中,我将帮助你准备好在下一次面试中解决这些与C# 编程语言相关的问题。同时,你可能想练习一些C# 项目。这 60 个基本的 C#面试问题和答案将帮助你了解该语言的技术概念。 目录 什么是 C#? 1.什么是类? 2.面向对象编程的主要概念是什么?...

11:STM32---spl通信

目录 一:SPL通信 1:简历 2:硬件电路 3:移动数据图 4:SPI时序基本单元 A : 开/ 终条件 B:SPI时序基本单元 A:模式0 B:模式1 C:模式2 D:模式3 C:SPl时序 A:发送指令 B: 指定地址写 C:指定地址读 二: W25Q64 1:简历 2: 硬件电路 3:W25Q64框图 4: Flash操作注意…...

kafka的 ack 应答机制

目录 一 ack 应答机制 二 ISR 集合 一 ack 应答机制 kafka 为用户提供了三种应答级别&#xff1a; all&#xff0c;leader&#xff0c;0 acks &#xff1a;0 这一操作提供了一个最低的延迟&#xff0c;partition的leader接收到消息还没有写入磁盘就已经返回ack&#x…...

Django系列:Django开发环境配置与第一个Django项目

Django系列 Django开发环境配置与第一个Django项目 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/details/1328…...

iPad协议/微信协议最新版

一、了解微信的协议 在开发微信协议之前&#xff0c;需要先了解微信的协议。微信的协议包括登录协议、消息传输协议、文件传输协议、数据同步协议等。其中&#xff0c;登录协议是最重要的协议之一&#xff0c;包括登录验证、登录认证等。消息传输协议则是微信最核心的功能之一…...