进程间通信(IPC)

简介

目的

1、数据传输:进程可以把数据给另一个进程。

2.资源共享:多个进程之间共享相同的资源

3.通知事件:进程可以向另一个或者一组进程发送消息,通知发生了某事。比如子进程终止时要通知父进程。

4.进程控制:有些进程完全控制另一个进程的执行时,进程可以帮另一个进程拦截所有陷入和异常,并能够及时知道它的状态。

注:进程间通信的前提是:不同进程间可以看到同一份资源。

发展

1.管道

* 匿名管道pipe

* 命名管道

2.System V进程间通信

* System V 消息队列

* System V共享内存

* System V信息量

3.POSIX进程间通信

* 消息队列

* 共享内存

*信息量

* 互斥量

* 条件变量

* 读写锁

今天主要讲前面两种——管道和System V进程通信。

管道

含义

管道是Unix中最古老的进程通信方式,我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。

在之前的学习中我们运用到了一条命令:

ps aux|  grep test | grep -v grep;

在这条命令中我们运用到了管道(“|”),这条命令就是把ps命令的内容放入到管道中,管道通过标准输入,输入到grep命令中,进行grep命令,再把处理好的文件打印到显示器上。

父进程执行ps aux,子进程执行grep命令,所以这条命令相当于父子进程间通信。

而父子间能够看到同一份资源,是因为shell创建一个管道,让父进程的标准输出(stdout)和子进程的标准输入(stdin)都指向这个管道,从而实现数据的传递——这本质上就是父子进程共享了“管道”这份资源。

这种管道也称为匿名管道。

匿名管道

创建匿名管道

#include <unistd.h>

int pipe(int pipefd[2]);
功能:创建一无名管道

参数:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端

返回值:成功返回0,失败返回错误代码

一般只有“血缘关系”的进程才能创建匿名管道。通常是父子进程、兄弟进程。

实例:父进程从键盘中读数据并写入管道,子进程读取管道写到显示器上。

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
    int fds[2];
    int ret=pipe(fds);
    pid_t id=fork();//在创建管道之后才能创建子进程,因为子进程要继承父进程的管道描述符
    if(ret!=0)
    {   
        perror("pipe");
        return 1;
    }   
        char buf[100];
        char str[100];
        int len=sizeof(buf);
        memset(buf,0x00,sizeof(buf));
    if(id==0)
    {   
        //子进程
        close(fds[1]);
        ssize_t n;
        while((n=read(fds[0],buf,len-1))>0)
        {   

            buf[n]='\0';
            printf("%s",buf);
        }   
        close(fds[0]);
        return 0;

    }   
    else if (id>0)
    {   
        //父进程
        close(fds[0]);
        while(fgets(str,100,stdin))
        {   
            len=strlen(str);
            if(write(fds[1],str,len)!=len)
            {   
                perror("write");
                break;
            }   
        }
        close(fds[1]);
        wait(NULL);
    }
    else
    {
        perror("fork");
        return 1;
    }
    return 0;
}

父子进程关闭其中一个端口的底层原理:

如何记住写端是fds[1],读端是fds[0]?

就把它想象成一支笔,可以写字;而0就想象成张着的嘴巴,正在读书。

底层原理

从文件描述符的角度——父子进程进行通信的底层原理:

内核角度

关闭各自的文件描述符后,管道形成了一个单向通信的通道,可以防止误操作。

匿名管道的五大特性

1.常用与血缘关系的进程,进行进程间通信,通常为父子进程,兄弟进程

2.单向通信

3.管道的生命周期随进程。管道的生命周期结束的标志是:所有引用它的文件描述符被关闭。即进程终止,管道也被销毁。

4.面向字节流。写入数据时,若缓冲区已满,写操作会阻塞(默认阻塞模式);读取数据时,若缓冲区为空,读操作会阻塞,直到有数据写入或写端关闭。

5.管道自带同步机制。即读写端状态同步。

 

管道的读写规则

当没有数据可读时

O_NONBLOCK disable(关闭管道的非阻塞模式):read调用阻塞,即进程暂停执行,一直等到有数据来到为止。

O_NONBLOCK enable(开启管道的非阻塞模式):read调用返回-1,errno值为EAGAIN。

当管道满的时候

O_NONBLOCK disable:write调用阻塞,直到有进程读走数据

O_NONBLOCK enable:调用返回-1,errno值为EAGAIN

如果所有管道写端对应的文件描述符被关闭,则read返回0

如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出

当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。

当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

匿名管道的四种情况

1.管道满了,写端持续写入。

结果:写满了就不再写入,写端被阻塞。

原因:管道的空间有限,最大为65536字节。

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define Dug 1024

int main()
{
    int count=0;
    int fds[2];
    if((pipe(fds))==-1)
    {   
        perror("pipe");
        return 1;
    }   
    char buf[Dug];
    memset(buf,'a',Dug);
    while(1)
    {   
        write(fds[1],buf,1);
        count++;
        printf("count=%d\n",count);
    }   
    close(fds[0]);
    close(fds[1]);
}

2.管道空了,读端持续读取。

结果:读端会被阻塞。

原因:阻塞机制是管道自带的同步特性,确保读进程不会“空读”无效数据。

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define Dug 1024

int main()
{
    int fds[2];
    if((pipe(fds))==-1)
    {   
        perror("pipe");
        return 1;
    }   
    char buf[Dug];
    while(1)
    {   
        read(fds[0],buf,1);
    }   
    close(fds[0]);
    close(fds[1]);
}

3.写端关闭,读取正常读取。

结果:read读取到文件结尾就返回0。

原因:内核通过返回0通知写端终止,避免读进程无限阻塞等待数据。

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define Dug 24

int main()
{
    int ret=0;
    int fds[2];
    if((pipe(fds))==-1)
    {   
        perror("pipe");
        return 1;
    }   
    char buf[Dug];
    memset(buf,'a',Dug);
    write(fds[1],buf,Dug);
    close(fds[1]);
    char str[Dug];  
    while(1)
    {   
        ssize_t n=read(fds[0],&str[ret],1);
        if(n==0)
        {   
            printf("一共读取了 %d \n",ret);
            break;
        }   
        else if(n==-1)
        {   
            perror("read");
            break;
        }   
        else {
            ret++;
            if(ret>Dug)
            {   
                break;
            }   
        }   
    }   
    printf("%s\n",str);
    close(fds[0]);
    return 0;
}
结果:
[root@localhost d3]# ./test_pipe
一共读取了 24 
aaaaaaaaaaaaaaaaaaaaaaaa

4.读端关闭,写端正常写入。

结果:内核会向进程发送SIGPIPE信号,让进程终止,同时write()返回错误码EPIPE(“管道破裂”)

原因:管道的写操作依赖读端存在,若读端已全部关闭,数据写入后无人接收,导致资源浪费。

创建线程池处理任务

线程池中父进程通过管道给子进程发布任务,任务的最终结果要反馈给父进程。但是当没有任务时子进程可以忙自己的事情。

ProcessPool.hpp

#ifndef _PROCESS_POOL_HPP_
#define _PROCESS_POOL_HPP

#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string>
#include<vector>
#include<cstdlib>
#include<time.h>
#include"task.hpp"
#include<functional>
const int gdefault_process_num=5;
//typedef std::funtion<void(int fd)> callback_t;
//只要返回值是void类型,参数是fd类型的参数
using callback_t=std::function<void(int fd)>;

//描述通道
class Channel
{
public:
    Channel()
    {}  
    Channel(int fd,std::string name,pid_t target):_wfd(fd),_name(name),_sub_target(target){}
    int Fd(){return _wfd;}
    std::string Name(){return _name;}
    pid_t Target(){return _sub_target;}
    void Close(){close(_wfd);}
    void Wait()
    {   
        int ret=waitpid(_sub_target,nullptr,0);
        (void)ret;//消除“未使用变量”的编译警告
    }   
private:
    int _wfd;//读写端进程描述符
    std::string _name;//管道名
    pid_t _sub_target;//管道对应的目标子进程
};
//线程池
class ProcessPool
{
private:
        void CtrlSubProcessHelper(int& index)
        {   
            //选择一个管道
                int who=index;
                index++;
                index%=_Channels.size();
                //选择一个任务
                int x=rand()%tasks.size();
                //任务推送给子进程
                std::cout<<"选择管道"<<_Channels[who].Name()<<",subtarget:"<<_Channels[who].Target()<<std::endl;
                write(_Channels[who].Fd(),&x,sizeof(x));//把x写入管道
                sleep(1);
        }
public:
    ProcessPool(int num=gdefault_process_num):processnum(num)
    {
        //确保子进程ID随机
        srand(time(nullptr)^getpid()^0x777);
    }
    ~ProcessPool(){}
    //初始化线程池
    bool InitProcessPool(callback_t cb)
    {
        for(int i=0;i<processnum;i++)
        {
            //创建管道
            int pipefd[2];
            if(pipe(pipefd)<0)
            {
                return false;
            }
            //创建子进程
            pid_t id=fork();
            if(id<0)
            {
                return false;
            }
            else if(id==0)
            {
                //子进程读取任务
                close(pipefd[1]);
                cb(pipefd[0]);//从pipefd[0]中读取类型为void,形参为int的类型的函数
                exit(0);
            }
            else
            {
                //父进程
                close(pipefd[0]);
                std::string name="Channel-"+std::to_string(i);//每个通道命名为Channel-1等;
                _Channels.emplace_back(pipefd[1],name,id);//把初始化了的管道写入
            }
        }
        return true;
    }
    //2.控制唤醒指定的子进程,让子进程完成指定任务
    //有五个子进程,可以用轮询的方式来提高完成效率

    //不指定任务的个数
    void PollingCtrlSubProcess()
    {
        int index=0;
        while(true)
        {
            CtrlSubProcessHelper(index);
        }
    }
    //指定任务个数
    void PollingCtrlSubProcess(int count)
    {
        if(count<0)
        {
            return ;
        }
        int index=0;
        while(count--)
        {
            CtrlSubProcessHelper(index);
        }
    }
    //等待子进程退出
    void WaitSubProcess()
    {
        for(auto &c:_Channels)
        {
            c.Close();
        }
        for(auto&c:_Channels)
        {
            c.Wait();
        }
    }
private:
    //组织管道
    std::vector<Channel> _Channels;//管道
    int processnum;//子进程数量
};
#endif

task.hpp

#pragma once
#include<iostream>
#include<functional>
#include<vector>
using task_t=std::function<void()>;
using namespace std;
void Download()
{
    cout<<"这是一个下载任务"<<endl;
}
void MySql()
{
    cout<<"这是一个MySql任务"<<endl;
}
void Sync()
{
    cout<<"这是一个数据刷新同步任务"<<endl;
}
void Log()
{
    cout<<"这是一个日志保存任务"<<endl;
}
std::vector<task_t> tasks;
class Init
{
    public:
        Init()
        {   
            tasks.push_back(Download);
            tasks.push_back(MySql);
            tasks.push_back(Sync);
            tasks.push_back(Log);
        }   
};
Init ginit;

main.cc

#include"ProcessPool.hpp"
int main()
{
    //1.创建进程池
    ProcessPool pp(5);
    //2.初始化进程池
    pp.InitProcessPool([](int fd){
    while(true)
    {   
        int code=0;//任务码
        ssize_t n=read(fd,&code,sizeof(code));
        if(n==sizeof(code))
        {   
            std::cout<<"子进程被唤醒,getpid为"<<getpid()<<std::endl;
            if(code>=0&&code<tasks.size())
            {   
                tasks[code]();
            }   
            else
            {   
                std::cerr<<"任务码不正确"<<endl;
            }   
        }   
        else if(n==0)
        {   
            std::cout<<"子进程应该退出了:geipid="<<getpid()<<std::endl;
            break;
        }   
        else
        {   
            std::cerr<<"read fd:"<<fd<<",error"<<std::endl;
            break;
        }   
    }   
    }); 
    //3.控制线程池
    pp.PollingCtrlSubProcess(10);
    //4.结束线程池
    pp.WaitSubProcess();
    std::cout<<"父进程控制子进程完成,父进程结束"<<std::endl;

    return 0;
}

makefile

processpool:main.cc
    g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
    rm -f processpool

命名管道

它可以在两个不相干的进程之间交换数据,这是和匿名管道之间的主要区别。

而且命名管道是一个特殊的文件。

创建命名管道

有两种方法可以实现:

1.在命令行上输入命令

mkfifo  filename

2.在程序中创建,相关函数有:

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

//mode是文件的权限

实例:

#include<stdio.h>
int main()
{
    mkfifo("p2",0644);
    return 0;
}

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

1.匿名管道本质上是个文件缓冲区,而命名管道本质上是个特殊文件。

2.匿名管道直接用pipe函数创建并打开,命名管道用mkfifo函数创建,用open函数打开。

3.匿名管道是两个有“血缘关系”的两个进程间的通信,命名管道是用于两个不相干的进程之间的通信。

管道的读写规则

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

O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO

O_NONBLOCK enable:立刻返回成功

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

O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO

O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

用命名管道实现文件拷贝

把文件中的内容写入管道

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
 int main()
{
    int infd=open("abc",O_CREAT|O_TRUNC|O_WRONLY,0644);
    if(infd<0)
    {   
        perror("open");
        return 1;
    }   
    char buf[1024];
    memset(buf,'a',1024);
    write(infd,buf,1024);
    close(infd);
    int in=open("abc",O_RDONLY);
    if(in<0)
    {   
        perror("open");
        return;
    }   
    mkfifo("tb",0644);
    char str[102];
    int fd=open("tb",O_WRONLY);
    int n;
    while((n=read(in,str,sizeof(str)-1))>0)
    {   
        write(fd,str,n);
    }   
    close(in);
    close(fd);
    return 0;
    
}

如果没有进程读取,该写端就会阻塞。

另一个文件读取管道

#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
    int out =open("abc.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);
    int fd=open("tb",O_RDONLY);
    if(out<0||fd<0)
    {   
        perror("open");
        return ;
    }   
    char str[102];
    int n;
    while((n=read(fd,str,sizeof(str)-1))>0)
    {   
        write(out,str,n);
    }   
    close(out);
    close(fd);
    return 0;

}

两个进程分别在两个终端执行,一个把内容传入到管道,一个把管道中的内容传入文件。

System V共享内存

System V共享内存的原理是:将内核的一块物理内存映射到多个进程的虚拟地址空间,使进程无需通过内核即可直接读写共享数据,从而实现高效通信。

共享内存的数据结构

struct shmid_ds

{

struct ipc_perm shm_perm; // 权限控制(关键字段)

size_t shm_segsz; // 共享内存段大小(字节)

pid_t shm_lpid; // 最后操作该段的进程ID

pid_t shm_cpid; // 创建该段的进程ID

shmatt_t shm_nattch;// 当前附加(映射)的进程数

time_t shm_atime; // 最后一次附加(shmat)的时间

time_t shm_dtime; // 最后一次分离(shmdt)的时间

time_t shm_ctime; // 最后一次修改的时间

// 其他填充字段...

};

共享内存的函数

shmget函数

原型

        #include<sys/shm.h>

        #include<sys/ipc.h>

        int shmget(key_t key, size_t size, int shmflg);

参数

key: 这个共享内存段名字,通过ftok函数获取。

#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);

size: 共享内存大小

shmflg: 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的

- 取值为IPC_CREAT:共享内存不存在,创建并返回;共享内存已存在,获取并返回。

- 取值为IPC_CREAT | IPC_EXCL:共享内存不存在,创建并返回;共享内存已存在,出错返回。

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

shmat函数

功能:将共享内存段连接到进程地址空间

原型

        #include<sys/ipc.h>

        #include<sys/types.h> 

        void *shmat(int shmid, const void *shmaddr, int shmflg);

参数

shmid: 共享内存标识

shmaddr: 指定连接的地址

shmflg: 它的两个可能取值是SHM_RND和SHM_RDONLY,0表示可读可写

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

说明:

- shmaddr为NULL,核心自动选择一个地址

- shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址

- shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)

- shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

shmdt函数

功能:将共享内存段与当前进程脱离

原型

        #include <sys/types.h>
       #include <sys/shm.h>

           int shmdt(const void *shmaddr);

参数

shmaddr: 由shmat所返回的指针

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

注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数

功能:用于控制共享内存

原型

        #include <sys/ipc.h>
       #include <sys/shm.h>

        int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数

shmid: 由shmget返回的共享内存标识码

cmd: 将要采取的动作(有三个可取值)

buf: 指向一个保存着共享内存的模式状态和访问权限的数据结构

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

cmd的三个取值:

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

总结:共享内存通信的总流程

1.生成键值:ftok函数

2.创建/获取共享内存:shmget函数

3.连接到内存:shmat函数

4.进程间通信

5.分离共享内存:shmdt函数

6.删除共享内存:shmctl函数

共享内存实现进程通信

A进程读取,B进程写入。 B进程创建共享内存,先写入,防止遗漏字母或者打印乱码。

//shm.cpp
#ifndef __SHM_HPP__
#define __SHM_HPP__
#include<iostream>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<string>
#include<cstdio>
std::string pathname=".";
int gproj_id=0x66;
int gdefaultsize=4096;//4KB,共享内存的大小
class SharedMemory
{
    private:
        bool CreatHelper(int flags)
        {   
            //获取键值,shmget的第一个参数
            _key=ftok(pathname.c_str(),gproj_id);
            if(_key<0)
            {   
                perror("ftok");
                return false; 
            }   
            _shmid=shmget(_key,_size,flags);
            if(_shmid<0)
            {   
                perror("shmget");
                return false;
            }   
            return true;
        }   
    public:
        SharedMemory(int size=gdefaultsize)
        :_key(0),_size(size),_shmid(-1),
        _start_addr(nullptr),_num(nullptr),_datastart(nullptr),
        _windex(0),_rindex(0)
        {}  
        bool Creat()
        {   
                return CreatHelper(IPC_CREAT|IPC_EXCL|0666);//共享内存存在,出错并返回
        }   
        bool Get()
        {   
            return CreatHelper(IPC_CREAT);//共享内存存在,获取并返回
        }   
        //连接,将指定的共享内存挂接到自己的进程地址空间
        bool Attch()
        {
            _start_addr=shmat(_shmid,nullptr,0);
            if((long long)_start_addr==-1)
            {
                perror("shmat");
                return false;
            }
            printf("_start_addr:%p\n",_start_addr);
            _num=(int*)_start_addr;
            _datastart=(char*)_start_addr+sizeof(int);
            return true;
        }
        void SetZero()
        {
            *_num=0;
        }
        //共享内存段和当前进程分离
        bool Detach()
        {
            int n=shmdt(_start_addr);
            if(n<0)
            {
                perror("shmdt");
                return false;
            }
            return true;
        }
        //写入字符
        void AddChar(char &ch)
        {
            if(*_num==_size)
            {
                return ;
            }
            ((char*)_datastart)[_windex++]=ch;
            ((char*)_datastart)[_windex]='\0';
            _windex%=_size;
            (*_num)++;
        }
        void PopChar(char &ch)//必须引用,因为对ch进行了操作,把值修改了
        {
            if(*_num==0)
            {
                return ;
            }
            ch=((char*)_datastart)[_rindex++];
            _rindex%=_size;
            (*_num)--;
        }
        bool RemoveShm()
        {
            int n=shmctl(_shmid,IPC_RMID,nullptr);
            if(n<0)
            {
                perror("shmctl");
                return false;
            }
            return true;
        }
        void PrintAttr()
        {
            struct shmid_ds buf;
            int n=shmctl(_shmid,IPC_STAT,&buf);
            if(n<0)
            {
                perror("shmctl");
                return ;
            }
            printf("key:0x%x\n",buf.shm_perm.__key);
            printf("size:%ld\n",buf.shm_segsz);
            printf("atime:%lu\n",buf.shm_atime);
            printf("nattch:%ld\n",buf.shm_nattch);
        }
        ~SharedMemory(){}
    private:
        key_t _key;//键值
        int _shmid;//共享内存段的标识码
        int _size;//共享内存的大小
        void *_start_addr;//共享内存段的起始地址
        int *_num;//共享内存中有效个数
        char *_datastart;//共享内存中实际存储数据的起始地址
        int _windex;//记录下一次写入数据的位置
        int _rindex;//记录下一次读取数据的位置
};
#endif

A:对共享内存进行读取

//A.cpp
#include"shm.hpp"
#include<unistd.h>
int main()
{
    SharedMemory shm;
    shm.Get();
    shm.Attch();
    shm.PrintAttr();
    while(true)
    {
        char c;
        //读取
        shm.PopChar(c);
        printf("A get char:%c\n",c);
        sleep(1);
        if(c=='Z')
            break;
    }
    shm.Detach();
    shm.RemoveShm();
    return 0;
}

B:对共享进程进行写入

//B.cpp
#include"shm.hpp"
#include<unistd.h>
int main()
{
    SharedMemory shm;
    shm.Creat();
    shm.Attch();
    shm.SetZero();
    char c='A';
    for(;c<='Z';c++)
    {   
        shm.AddChar(c);
        sleep(1);
    }   
    shm.Detach();
    return 0;
}

makefile

.PHONY:all
all: A B //all前面没有空格
A:A.cpp
    g++ -o $@ $^ -std=c++11
B:B.cpp
    g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
    rm -f A B

两个进程分别在两个终端运行,先运行B进程(写入),在运行A进程(读取)。

查询建立的共享内存命令:ipcs -m

删除共享内存命令:ipcrm -m +shmid号

注:nattch是关联的进程数,如果两个进程用共享内存关联了,那么nattch等于2;如果只是创建成功共享内存但是连接失败,那么就是0;如果只有一个进程关联了,那么就是1。

特征总结

1.生命周期随内核。

2.共享内存通信是进程间通信中速度最快的。因为它减少了拷贝的次数,也不需要调用系统调用。

其他IPC方式(如管道、消息队列)需要将数据从用户空间拷贝到内核空间,在从内核空间拷贝到目标进程的用户空间,共两次拷贝。而共享内存只需要让多个进程访问同一块内核内存区域,无需任何数据拷贝。

进程创建共享内存映射后,后续访问共享内存时,都是在用户空间操作,无需频繁调用系统调用。

3.共享内存通信是没有同步,互斥机制。但是这会导致多个进程同时出现“数据竞争”问题,所以需要开发者自行搭配同步工具。

常用的有:

信息量(Semaphore):用于实现进程间的互斥(保证同一时间只有一个进程写)和同步(控制进程读写顺序)。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值