在这里插入图片描述

进程间通信介绍

进程间通信目的

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

进程间通信发展

进程间通信(IPC)的发展经历了多个阶段,主要包括:

随着技术发展,IPC机制也在不断进步,以满足更高效、更安全的进程通信需求。全部的进程间通信虽然体系庞大,但是其基本思想都离不开操作系统的性质–先描述,再组织我们只需学习早期的通信方法管道和System V通信,触类旁通。

管道

什么管道

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

在这里插入图片描述

匿名管道

匿名管道(Anonymous Pipe)是一种在进程间进行单向通信的机制,其中一个进程作为写入端,另一个进程作为读取端。它是一种简单有效的进程间通信方式,常用于父子进程或兄弟进程之间的通信。

在Linux系统中,可以使用pipe函数创建匿名管道。pipe函数创建一个管道,并返回两个文件描述符,一个用于读取数据,另一个用于写入数据。其中,文件描述符0表示标准输入stdin),文件描述符1表示标准输(stdout)。

#include <unistd.h>
int pipe(int fd[2]);
//创建失败返回-1,创建成功返回两个文件描述符构成的数组

以下是使用匿名管道进行进程间通信的基本步骤

  1. 调用pipe函数创建管道,获取读取端和写入端的文件描述符
  2. 调用fork函数创建一个子进程。
  3. 在父进程中关闭读取端的文件描述符,保留写入端的文件描述符
  4. 在子进程中关闭写入端的文件描述符,保留读取端的文件描述符。
  5. 父进程可以通过写入端的文件描述符向管道写入数据。
  6. 子进程可以通过读取端的文件描述符从管道读取数据

下面是一个简单示例代码演示如何使用匿名管道进行进程间通信:

#include <stdio.h&gt;
#include <unistd.h&gt;

int main() {
    int pipefd[2];
    char buffer[20];
    pid_t pid;

    // 创建管道
    if (pipe(pipefd) == -1) {
        perror("pipe");
        return 1;
    }

    // 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork");
        return 1;
    }

    if (pid == 0) {
        // 子进程
        close(pipefd[1]);  // 关闭写入

        // 从管道读取数据
        read(pipefd[0], buffer, sizeof(buffer));
        printf("子进程接收到的数据:%sn", buffer);

        close(pipefd[0]);  // 关闭读取端
    } else {
        // 父进程
        close(pipefd[0]);  // 关闭读取端

        // 向管道写入数据
        write(pipefd[1], "Hello, child process!", 21);

        close(pipefd[1]);  // 关闭写入
    }

    return 0;
}

上述示例中,父进程向管道写入了一段字符串,子进程从管道中读取该字符串并输出。通过匿名管道,父子进程之间实现简单的通信。
需要注意的是,匿名管道只能实现单向通信,如果需要双向通信可以考虑使用命名管道(Named Pipe)或其他进程间通信机制,如共享内存消息队列。

fork共享管道原理

在这里插入图片描述

站在文件描述符角度-深度理解管道

在这里插入图片描述

站在内核角度-管道本质

在这里插入图片描述
所以,看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想”。

管道读写规则

没有数据可读时

  1. O_NONBLOCK disableread调用阻塞,即进程暂停执行,一直等到有数据来到为止。
  2. O_NONBLOCK enableread调用返回-1,errno值为EAGAIN。
    当管道满的时候
  3. O_NONBLOCK disablewrite调用阻塞,直到有进程读走数据
  4. O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
如果所有管道写端对应的文件描述符被关闭,则read返回0
如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程
退出
当要写入的数据量大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

管道特点

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

命名管道

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

创建一个命名管道

  1. 命名管道可以从命令行上创建,命令行方法是使用下面这个命令
mkfifo filename

其中,filename 是要创建的命名管道的路径名称
例如,要在命令行上创建一个名为myfifo” 的命名管道,可以执行以下命令

mkfifo myfifo

执行命令后,系统将在当前目录下创建一个名为myfifo” 的命名管道文件。

需要注意的是,命名管道是一种特殊的文件可以像普通文件一样进行读写操作。在命令行中创建的命名管道可以被其他进程打开并进行读写操作

  1. 命名管道也可以从程序里创建,相关函数有:
    mkfifo 是一个用于创建命名管道(Named Pipe)的系统调用。它可以在文件系统中创建一个特殊的文件,用于进程间的通信。

具体使用方法如下

#include <sys/types.h&gt;
#include <sys/stat.h&gt;

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

返回值

使用示例

#include <stdio.h&gt;
#include <sys/types.h>
#include <sys/stat.h>

int main() {
    const char *pathname = "myfifo";
    mode_t mode = 0666; // 设置权限读写所有者、组和其他用户

    if (mkfifo(pathname, mode) == -1) {
        perror("mkfifo");
        return 1;
    }

    printf("命名管道创建成功:%sn", pathname);

    return 0;
}

上述示例中,我们调用了 mkfifo 函数创建了一个命名管道,并指定路径名称为 “myfifo”,权限模式为 0666,表示读写所有者、组和其他用户都有权限。

需要注意的是,命名管道是一种特殊的文件,可以像普通文件一样进行读写操作。在进程间通信时,一个进程可以将数据写入命名管道,而另一个进程可以从该管道读取数据。但是,需要注意管道的读取和写入操作应该不同的进程中进行,否则可能会造成阻塞

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

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

命名管道的打开规则

对于命名管道(FIFO)的打开规则,取决于打开操作是为读还是为写。

  1. 当打开操作为读时:

    • 如果打开操作没有设置 O_NONBLOCK 标志(即阻塞模式),则打开操作会阻塞,直到有其他进程以写模式打开该命名管道。
    • 如果打开操作设置O_NONBLOCK 标志(即非阻塞模式),则打开操作会立即返回成功,不会阻塞等待
  2. 当打开操作为写时:

这些规则确保了在读写操作之间建立正确的通信连接。如果读操作和写操作同时进行,并且满足打开规则,那么数据可以在进程之间通过命名管道进行传输

需要注意的是,打开规则只适用于命名管道的打开操作,而不是创建操作。创建命名管道时,并不需要等待其他进程的打开操作。只有在打开操作时,才会根据规则进行阻塞或立即返回。

命名管道的删除

用命名管道实现文件拷贝

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<fcntl.h>
#include<sys/wait.h>
#include<stdlib.h>
#define BUFFER_SIZE 50
int main()
{
    const char *pathname = "myfifo";
    mode_t mode = 0666; // 设置权限为读写所有者、组和其他用户
    if (mkfifo(pathname, mode) == -1)
    {
        perror("mkfifo");
        return 1;
    }
    printf("命名管道创建成功:%sn", pathname);
    // 打开源文件目标文件    
    int source_fd = open("./text.txt", O_RDONLY);
    if (source_fd == -1) {
        perror("open source file");
        return 1;
    }

    int destination_fd = open("./popytext.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (destination_fd == -1) {
        perror("open destination file");
        return 1;
    }
    // 打开命名管道
    int fifo_fd = open("myfifo", O_RDWR);
    if (fifo_fd == -1) {
        perror("open fifo");
        return 1;
    }
    
    // 创建子进程进行数据传输
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        return 1;
    }
    char buffer[BUFFER_SIZE];
    ssize_t bytes_read;
    if (pid == 0) 
    {
        // 子进程从命名管道中读取数据并写入目标文件
        if((bytes_read = read(fifo_fd, buffer, BUFFER_SIZE)) > 0) {
            write(destination_fd, buffer, bytes_read);
        }
        exit(EXIT_SUCCESS);
    }
    // 父进程从源文件读取数据并写入命名管道
    if((bytes_read = read(source_fd, buffer, BUFFER_SIZE)) > 0) {
        write(fifo_fd, buffer, bytes_read);
    }
    close(source_fd);
    close(fifo_fd);
    close(destination_fd);
    // 等待子进程结束
    wait(NULL);
    // 删除命名管道
    unlink("myfifo");

    return 0;
}

用命名管道实现server&amp;client通信

comm.hpp

#pragma once 
#include<iostream>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>

#define FIFO_FILE "./myfifo"
#define MODE 0664
enum{
    FIFO_CREATE_ERR = 1,
    FIFO_DELETE_ERR, 
    FILE_OPEN_ERR
};
class Init
{
public:
    Init()
    {
        int n = mkfifo(FIFO_FILE,MODE);
        if(n == -1)
        {
            //printf("%d:%sn",errno,strerror(errno));
            perror("mkfifo");
            exit(FIFO_CREATE_ERR);
        }
    }
    ~Init()
    {
        int m = unlink(FIFO_FILE);
        if(m== -1)
        {
            perror("unlink");
            exit(FIFO_DELETE_ERR);
        }
    }
};

log.hpp

#pragma once

#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define SIZE 1024

#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

#define Screen 1
#define Onefile 2
#define Classfile 3

#define LogFile "log.txt"

class Log
{
public:
    Log()
    {
        printMethod = Screen;
        path = "./log/";
    }
    void Enable(int method)
    {
        printMethod = method;
    }
    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }
    void printLog(int level, const std::string &amp;logtxt)
    {
        switch (printMethod)
        {
        case Screen:
            std::cout << logtxt << std::endl;
            break;
        case Onefile:
            printOneFile(LogFile, logtxt);
            break;
        case Classfile:
            printClassFile(level, logtxt);
            break;
        default:
            break;
        }
    }
    void printOneFile(const std::string &amp;logname, const std::string &amp;logtxt)
    {
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
        if (fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }
    void printClassFile(int level, const std::string &amp;logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
        printOneFile(filename, logtxt);
    }

    ~Log()
    {
    }
    void operator()(int level, const char *format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&amp;t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        // 格式默认部分+自定义部分
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %sn", leftbuffer, rightbuffer);

        // printf("%s", logtxt); // 暂时打印
        printLog(level, logtxt);
    }

private:
    int printMethod;
    std::string path;
};


server.cc

#include"comm.hpp"
#include"log.hpp"

using namespace std;

//管理管道文件
int main()
{
    
    /*logmessage(Info,"hello linux");*/
    //创建信道
    Init init;
    //打开管道
    int fd = open(FIFO_FILE,O_RDONLY);
    if(fd<0)
    {
        //perror("open");
        logmessage(Fatal,"error string:%s,error code:%d",strerror(errno),errno);
        exit(FILE_OPEN_ERR);
    }
    logmessage(Info,"error string:%s,error code:%d",strerror(errno),errno);
    //开始通信
    while(true)
    {
        char buffer[1024]={0};
        int x = read(fd,buffer,sizeof(buffer));
        if(x>0)
        {
            buffer[x]=0;
            cout<<"client say#"<<buffer<<endl;
        }
        else if(x==0)
        {
            logmessage(Debug,"client quit,me too!:%s,error code:%d",strerror(errno),errno);
            break;
        }
        else
            break;
    }

    //关闭管道 
    close(fd);
    
    return 0;
}

client.cc

#include"comm.hpp"
using namespace std;

int main()
{
    int fd = open(FIFO_FILE,O_WRONLY);
    if(fd<0)
    {
        perror("open");
        exit(FILE_OPEN_ERR);
    }
    string line;
    while(true)
    {
        cout<<"Please Enter@";
        getline(cin,line);

        write(fd,line.c_str(),line.size());
    }
    close(fd);
    return 0;
}

system V共享内存

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

共享内存示意图

在这里插入图片描述

共享内存数据结构

struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};

这段代码定义struct shmid_ds 结构体,用于描述 System V 共享内存段的属性状态。让我们逐个解释这些字段的含义:

通过使用 struct shmid_ds 结构体,可以获取共享内存段的属性信息,例如大小、创建时间、连接数等。这些信息对于管理和监控共享内存的使用非常有用。

请注意,struct shmid_ds 结构体中的字段类型可能会因操作系统编译器而异,例如 __kernel_time_t__kernel_ipc_pid_t。在实际使用时,你需要查阅相关的系统文档头文件来了解具体的字段类型和定义。

共享内存函数

shmget函数

功能用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
- key:用于标识共享内存段的键值。可以使用 `ftok` 函数生成一个唯一键值- 也可以使用已知的键值获取现有的共享内存段。不同的共享内存段应该使用不同键值- size指定共享内存段的大小,以字节单位。共享内存段的大小应根据实际需求进行设置- shmflg:用于指定创建共享内存段的标志和访问权限。可以通过按位运算符 `|` 组合- 个标志。常用的标志包括:
- IPC_CREAT:如果共享内存段不存在,则创建一个新的共享内存段。  
- IPC_EXCL:不单独使用,与 IPC_CREAT 一起使用,如果共享内存段已存在,则返回错误- 0666指定共享内存段的访问权限。这里的权限是八进制表示,表示所有者- 所属组和其他用户读写权限。
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

上面的Key有讲究的:

  1. key是一个数字,这个数字是几,并不重要。关键在于它必须在内核具有唯一性,能够让不同的进程进行唯一标识
  2. 第一个进程可以通过key创建共享内存,第二个之后的进程,只要拿着同一个key就可以和第一个进程看到同一个共享内存了!
  3. 对于一个已经创建好的共享内存,key在哪? key在共享内存的描述对象中!
  4. 一次创建的时候,必须有一个key了。怎么有?可以使用 ftok 函数生成一个唯一键值,也可以使用已知的键值来获取现有的共享内存段。不同的共享内存段应该使用不同的键值
    ftok 函数用于将一个路径名和一个整数标识符转换为一个唯一键值,用于标识 System V 共享内存段、消息队列或信号量集。下面是 ftok 函数的使用方法:
#include <sys/types.h>
#include <sys/ipc.h>

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

ftok 函数将 pathnameproj_id 组合起来,生成一个唯一键值。该键值可以用于创建共享内存段、获取现有的共享内存段或操作其他 System V IPC 对象

需要注意的是,ftok 函数的结果并不是绝对唯一的,因为它使用了路径名和整数标识符组合。因此,在使用 ftok 生成键值时,应确保路径名和标识符组合在整个系统中是唯一的,以避免冲突

以下是一个示例,展示如何使用 ftok 函数生成一个键值:

#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>

int main() {
    const char *pathname = "/path/to/file"; // 路径名
    int proj_id = 1; // 标识

    key_t key = ftok(pathname, proj_id);
    if (key == -1) {
        perror("ftok");
        return 1;
    }

    printf("Generated key: %dn", key);

    return 0;
}

上述示例中,我们使用 /path/to/file 作为路径名,1 作为标识符,然后调用 ftok 函数生成一个键值。如果成功,将打印生成的键值。如果发生错误,将打印相应的错误信息

请注意,生成的键值应与其他进程共享,以便它们可以使用相同的键值访问相同的共享内存段。

shmat函数

功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针指向共享内存第一个节;失败返回-1

说明

  1. shmaddr为NULL,核心自动选择一个地址
  2. shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址
  3. shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址自动向下调整为SHMLBA的整数倍。公式:shmaddr -(shmaddr % SHMLBA)
  4. shmflg=SHM_RDONLY,表示连接操作用只读共享内存
    shmdt函数
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数

功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0失败返回-1

在这里插入图片描述

System V 共享内存的使用涉及以下几个步骤

  1. 创建共享内存段:使用 shmget 函数创建一个共享内存段。该函数接受三个参数:键值(用于标识共享内存段),大小指定共享内存的大小),和标志(用于指定权限和创建方式)。成功创建共享内存段后,shmget 函数返回一个唯一的标识符(共享内存标识符)。

  2. 连接共享内存:使用 shmat 函数将共享内存段连接到进程的地址空间shmat 函数接受两个参数:共享内存标识符和地址(如果地址为 NULL,则系统自动选择一个可用的地址)。成功连接共享内存后,shmat 函数返回共享内存的起始地址。

  3. 使用共享内存:一旦共享内存连接到进程的地址空间,进程可以像使用普通内存一样使用它。可以通过指针访问共享内存中的数据。

  4. 分离共享内存:使用 shmdt 函数将共享内存从进程的地址空间分离shmdt 函数接受一个参数,即共享内存的起始地址。成功分离共享内存后,进程将无法再访问共享内存中的数据。

  5. 删除共享内存段:使用 shmctl 函数删除共享内存段。shmctl 函数接受三个参数:共享内存标识符、命令(用于指定要执行的操作),和一个结构指针(用于传递额外的参数)。通过指定命令IPC_RMID,可以删除共享内存段。

下面是一个简单的 C 语言示例,展示如何使用 System V 共享内存:

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

int main() {
    key_t key = ftok("shared_memory_key", 1234);  // 创建一个唯一的键值

    int shm_id = shmget(key, sizeof(int), IPC_CREAT | 0666);  // 创建共享内存段
    if (shm_id == -1) {
        perror("shmget");
        return 1;
    }

    int *shared_data = (int *)shmat(shm_id, NULL, 0);  // 连接共享内存
    if (shared_data == (int *)-1) {
        perror("shmat");
        return 1;
    }

    *shared_data = 42;  // 在共享内存中写入数据

    printf("Shared data: %dn", *shared_data);

    shmdt(shared_data);  // 分离共享内存

    shmctl(shm_id, IPC_RMID, NULL);  // 删除共享内存段

    return 0;
}

在上面的示例中,我们使用 ftok 函数创建一个唯一的键值,用于标识共享内存段。然后我们使用 shmget 函数创建一个大小为 sizeof(int) 的共享内存段,并指定了创建标志 IPC_CREAT | 0666接下来我们使用 shmat 函数将共享内存连接到进程的地址空间,并将其强制转换int 类型指针然后我们可以通过指针访问共享内存中的数据。最后,我们使用 shmdt 函数分离共享内存,并使用 shmctl 函数删除共享内存段。

请注意,System V 共享内存是一种底层的机制,需要谨慎使用,以确保正确的同步和互斥。在实际应用中,你可能需要使用其他同步机制(如信号量互斥锁)来保护共享内存的访问。

system V信号量 - 选学了解即可

信号量主要用于同步和互斥的,下面先来看看什么是同步和互斥。
在多进程或多线程环境中,进程互斥(Process Mutual Exclusion)是指一种机制,用于确保同时只有一个进程(或线程)可以访问共享资源,以避免数据竞争和不一致的结果

多个进程或线程同时访问共享资源时,可能会出现以下问题

  1. 竞态条件(Race Condition):多个进程或线程不可预测方式相互干扰,导致结果正确性无法保证。

  2. 数据不一致:多个进程或线程共享资源进行读写操作时,可能会导致数据的不一致性,破坏程序正确性。

为了解决这些问题,需要使用进程互斥机制来确保共享资源的互斥访问。常见的进程互斥机制包括使用互斥锁(Mutex)、信号量(Semaphore)和临界区(Critical Section)等。

互斥锁是一种最常见的进程互斥机制,它提供了两个基本操作:上锁(Lock)和解锁(Unlock)。当一个进程或线程需要访问共享资源时,它会尝试上锁,如果成功获取到锁,则可以访问共享资源;如果锁已经被其他进程或线程持有,则该进程或线程会被阻塞,直到锁被释放。

通过使用互斥锁,可以确保同一时间只有一个进程或线程可以访问共享资源,从而避免了竞态条件和数据不一致的问题

需要注意的是,进程互斥只是一种机制,它并不能解决所有的并发问题。在设计并发程序时,还需要考虑其他因素,如死锁(Deadlock)和活锁(Livelock)等。

进程同步(Process Synchronization)是指在多个进程或线程之间协调和控制它们的执行顺序,以确保它们按照一定的顺序和时间间隔执行。

并发环境中,多个进程或线程可能会同时执行,并且彼此之间的执行速度顺序是不确定的。这可能导致一些问题,如竞态条件、数据不一致和死锁等。

进程同步的目的是通过使用同步机制来协调和控制进程或线程的执行,以避免这些问题。常见的进程同步机制包括互斥锁、信号量、条件变量屏障等。

以下是一些常见的进程同步场景

  1. 互斥访问共享资源:多个进程或线程需要访问共享资源时,通过使用互斥锁来确保同一时间只有一个进程或线程可以访问共享资源,避免数据竞争和不一致的结果

  2. 同步执行顺序:在某些情况下,需要确保多个进程或线程按照特定的顺序执行。例如,一个进程可能需要等待另一个进程完成某个任务后才能继续执行。这可以通过使用信号量或条件变量来实现。

  3. 生产者消费者问题:在生产者消费者问题中,多个生产者进程和消费者进程共享一个有限缓冲区生产者进程将数据放入缓冲区,而消费者进程从缓冲区中取出数据。需要确保生产者消费者之间的操作同步,以避免缓冲溢出下溢。这可以通过使用信号量来实现。

进程同步是并发编程中的重要概念,它可以帮助解决并发执行时可能出现的问题,确保程序正确性和一致性

总之以上是进程间通信的基本概念,在以后的学习中会慢慢深入进程更加详细知识

原文地址:https://blog.csdn.net/qq_51536567/article/details/134754248

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_40076.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注