system V IPC进程间通信机制一网打尽

简介: system V IPC进程间通信机制一网打尽

必备IPCS命令解析

  • ipcs  

功能 : 查看 system V IPC进程间通信设施的信息

常用选项

-a : 查看所有通信设施信息, 不加选项默认-a

-m : 指定查看共享内存

-q  : 指定查看消息队列

-s   : 指定查看信号量

  • ipcrm  
  • 功能:删除System V进程间通信(IPC)对象和关联的数据结构

使用方式:  ipcrm 选项 id号

常用选项

-m : 删除共享内存

-q : 删除消息队列

-s : 删除信号量

eg : 指定删除shmid = 18 和  msqid = 8的 IPC对象.

Linux IPC消息队列

消息队列提供了一个进程向另外一个进程传输一个数据块的方法


消息队列优势: 对比管道通信来看


避免了管道通信的同步阻塞问题    eg : 读进程从管道中读取数据, 但是管道中并没有数据可读, 此刻读进程就需要同步阻塞等待写进程写入数据.

消息队列不必像管道那样先进先出的接收数据了, 消息队列可以根据类型(type值)来有选择地接收数据

  • msgget

函数功能: 创建一个全局消息队列IPC对象.   (内核数据结构)


参数分析:  key 唯一标识, 唯一标识一个全局的消息队列.   msgflg指定权限


返回值: 成功返回一个正整数, 代表消息队列的句柄, 失败返回 -1,并且设置errno


插一个权限介绍:     r : 读权限    w : 可写权限   X : 可执行权限.   所谓的9位权限标识位, 就是针对: 文件拥有者, 文件拥有组人员, 其他人员的访问权限的限制

  • msgsnd

函数功能:发送一条消息到消息队列中

参数分析:

msqid 之前msgget返回的消息队列句柄


msgp 指针, 指向待发送的一条消息结构体, 结构体形式如下


msgsz  消息正文的存储空间, 最多可以存下msgsz个字节的正文


msgflg 控制发送消息的形式  eg: 常用的 IPC_NOWAIT 表示非阻塞发送, 意思就是说正常没有设置非阻塞的情况下, 如果消息队列满了, 就会阻塞等待, 但是设置了IPC_NOWAIT则会立刻返回, 并且设置errno = EAGAIN

struct msgbuf {
    long mtype;       /* message type, must be > 0 */
    char mtext[1];    /* message data */
};
//我们必须按照这个形式设置msgbuff

返回值:成功返回 0, 失败返回 -1 并且设置errno

  • msgrcv

函数功能:从消息队列中接收一条消息

参数分析:

分析:


msqid 之前msgget返回的消息队列句柄


msgp 指针, 指向待发送的一条消息结构体, 结构体形式如下


msgsz  消息正文的存储空间, 最多可以存下msgsz个字节的正文


msgflg 控制接收消息的形式  eg: 常用的 IPC_NOWAIT 表示非阻塞接收, 意思就是说正常没有设置非阻塞的情况下, 如果消息队列没有消息, 就会阻塞等待, 但是设置了IPC_NOWAIT则会立刻返回, 并且设置errno = ENOMSG

msgtyp 指定接受哪种类型的消息:

  1. 等于0,指定接收消息队列第一条消息
  2. 大于0,指定接收消息队列中第一条类型等于msgtyp的消息
  3. msgtyp 小于0, 读取消息队列中第一个类型值比msgtyp的绝对值小的消息

返回值:  成功返回接收的消息正文长度, 失败返回 -1 并且设置 errno

  • msgctl

函数功能: 控制消息队列, 对于消息队列IPC进行设置, 操作

参数分析:

  1. msqid 之前msgget返回的消息队列句柄
  2. cmd 对于消息队列的操作, 一般用的最多是 IPC_RMID,删除消息队列标识符

返回值:成功返回 0, 失败返回 -1 并且设置 errno

实际案例, 父子进程之间通信, 父进程向消息队列中写入消息, 子进程按照消息的类型接收消息

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <sys/wait.h>
#define ERR_EXIT(m) \
    do { perror(m); exit(EXIT_FAILURE); } while (0);
#define TEXTSIZE 1024
//消息结构体
typedef struct msgbuff {
  long int type;
  char  text[TEXTSIZE];
} msgbuff;
int main(int argc, char* argv[]) {
  if (2 != argc) {
    fprintf(stderr, "usage: %s <filepath>", argv[0]);
    exit(EXIT_FAILURE);
  }
  key_t key = ftok(argv[1], 'q');
  pid_t pid = fork();
  int msgid;
  if (pid == -1) {
    ERR_EXIT("fork");
  }
  if (pid == 0) {
    //son process
    //获取消息队列
    msgid = msgget(key, 0666 | IPC_CREAT);
    msgbuff msg;
    //然后接收类型为1的消息并且打印
    int read_size = msgrcv(msgid, &msg, TEXTSIZE, 1, 0);
    if (-1 == read_size) {
      ERR_EXIT("msgrcv");
    }
    printf("type: %d, text: %s\n", msg.type, msg.text);
    memset(&msg, 0, sizeof(msg));
    //再接收一下类型为2的消息并且打印
    read_size = msgrcv(msgid, &msg, TEXTSIZE, 2, 0);
    if (-1 == read_size) {
      ERR_EXIT("msgrcv");
    }
    printf("type: %d, text: %s\n", msg.type, msg.text);
  } else {
    //fa process
    //创建消息队列
    msgid = msgget(key, 0666 | IPC_CREAT);
    //创建消息结构体
    msgbuff msg1, msg2;
    memset(&msg1, 0, sizeof(msg1));
    memset(&msg1, 0, sizeof(msg2));
    msg1.type = 1;
    memcpy(msg1.text, "hello msg queue I am type1", strlen("hello msg queue I am type1"));
    msgsnd(msgid, &msg1, TEXTSIZE, 0);
    msg2.type = 2;
    memcpy(msg2.text, "hello msg queue, I am type2", strlen("hello msg queue, I am type2"));
    msgsnd(msgid, &msg2, TEXTSIZE, 0);
    waitpid(pid, NULL, 0);
      if (-1 == msgctl(msgid, IPC_RMID, NULL)) {
          ERR_EXIT("msgctl");
          exit(1);
      }
  }
  return 0;
}

Linux IPC信号量

  • 理解信号量

信号量是一种资源, 临界资源, 是为了多进程间或者多线程间的同步互斥问题而存在的.


信号量本身也是一个IPC对象,是临界共享资源, 利用它使得多进程或者多线程之间可以实现通信.


信号量本身就是具有原子性的计数器, 意思就是说对于信号量的操作是自带原子性的, 就不用担心多进程或者多线程互斥写操作的问题了.


何为同步问题: 同步, 两个事件之间具有同步的性质, 意味着两个事件之间是存在条件约束的, 是有等待关系的.    eg  B 的要发生必须先发生 A ,否则B只能挂起等待,  则称 A, B是同步的


Linux最常见的同步问题: 条件变量, 满足一个条件, 才能执行一个操作. 在满足条件之前挂起等待, 条件的满足其实依赖的是 另一个事件的发生.


通信的时候通信双方也必须保持同步,比如说共享内存就需要借助信号量这样的同步机制,维护通信双方进程之间的同步.


何为互斥问题:    多个进程或者线程对于同一份资源进行写操作, 写操作一定是互斥的, 即为同一时刻只能允许一个进程或者线程对这份资源进行一个写操作.

  • semget

函数功能:创建或者获取一个信号量集合IPC对象

参数分析:

  1. key :标识全局信号量集合
  2. nsems: 表示要创建/获取的信号量集中信号量的数目,如果是创建信号量,这个值必须指定。如果是获取已经存在的信号量,可以把它设置成0.
  3. semflg:指定权限

插一个权限介绍:     r : 读权限    w : 可写权限   X : 可执行权限.   所谓的9位权限标识位, 就是针对: 文件拥有者, 文件拥有组人员, 其他人员的访问权限的限制

返回值:成功返回信号集合句柄, 失败返回 -1 并且设置errno

  • semop

函数功能:对于信号量资源进行 P V 操作,改变信号量的值

参数分析:

semid 之前semget返回的信号量集合句柄


nsops:nsops参数指定要执行的操作个数,即是sops数组中元素的个数,semop对数组sops中的每个元素按照数组顺序依次执行操作,并且这个过程是原子操作。


sops 结构体指针, 指向struct sembuf 数组首地址 结构体定义如下

struct sembuf
{
  unsigned short int sem_num;/* semaphore number */
  short int sem_op; /* semaphore operation */
  short int sem_flg;/* operation flag */
};
  1. sem_flg 可选值是 IPC_NOWAIT, SEM_UNDO,IPC_NOWAIT指,无论信号量集操作是否成功,semop调用都立刻返回。并且设置errno = EAGAIN , SEM_UNDO含义是,当进程退出时,取消正在进行的semop操作      

返回值:成功返回 0, 失败返回 -1 并且设置 errno

  • semctl

函数功能:对于指定信号量IPC进行操作

参数分析:

semid 之前semget返回的信号量集合句柄


semnum 指定操作信号量在信号量集合中的编号, (The semaphores in a set are numbered starting at 0. 也就是说集合中的信号量编号从0开始.


cmd 对指定信号量执行的操作


有的命令需要传入第4个参数,这个参数类型由用户定义,但是,内核给出了它的固定形式

 union semun {
     int              val;    /* Value for SETVAL */
     struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
     unsigned short  *array;  /* Array for GETALL, SETALL */
     struct seminfo  *__buf;  /* Buffer for IPC_INFO
                             (Linux-specific) */
 };

cmd常用列举:

  1. SETVAL 将信号量的semval值设置为semun.val
  2. IPC_RMID 立即移除信号量集,唤醒所有等待信号量集的进程

返回值:成功返回 0, 失败返回 -1 并且设置 errno

实际案例:利用信号量实现一个简单的父子进程之间的同步等待, 通信, 也就是利用P V实现了跟锁一样的效果, 实现了父子进程同步打印, 不会出现乱入(汇编指令中间重入)问题, 保证原子性操作

#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <sys/wait.h>
#define ERR_EXIT(m) \
    do { perror(m); exit(EXIT_FAILURE); } while (0);
//联合结构体
union semun {
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO*/
};
int initsem(key_t key) {
    int semid = -1;
    //创建一个信号量集合IPC, 其中包含一个信号量
    if (-1 == (semid = semget(key, 1, 0666 | IPC_CREAT))) {
        ERR_EXIT("semget");
    }
    //设置信号量val
    union semun sem_un;
    sem_un.val = 1;
    if (-1 == semctl(semid, 0, SETVAL, sem_un)) {
        ERR_EXIT("semctl");
    }
    return semid;
}
//销毁
void destorysem(int semid) {
    if (-1 == semctl(semid, 0, IPC_RMID)) {
        ERR_EXIT("semctl destorysem");
    }
}
void P(int semid) {
    //因为信号量集合中就只是一个
    //所以sembuf无需设置为vector
    struct sembuf op;//操作
    op.sem_num = 0;
    op.sem_op = -1;// - 1
    op.sem_flg = SEM_UNDO;
    if (-1 == semop(semid, &op, 1)) {
        ERR_EXIT("semop P");
    }
}
void V(int semid) {
    struct sembuf op;//操作
    op.sem_num = 0;
    op.sem_op = 1;// + 1
    op.sem_flg = SEM_UNDO;
    if (-1 == semop(semid, &op, 1)) {
        ERR_EXIT("semop V");
    }
}
int main(int argc, char* argv[]) {
    if (2 != argc) {
        fprintf(stderr, "usage: %s <filepath>", argv[0]);
        exit(EXIT_FAILURE);
    }
    //1.获取唯一标识key
    key_t key = ftok(argv[1], 'a');
    //2. 创建信号量集合
    int semid = initsem(key);
    int cnt = 10;
    pid_t pid = fork();
    if (pid == -1) {
        ERR_EXIT("fork");
    }
    if (pid == 0) {
        //son process
      while (cnt > 0) {
         // P(semid);
          printf("I am son process\n");
          sleep(1);
          //V(semid);
          cnt -= 1; 
      }
    } else {
      //fa process
      while (cnt > 0) { 
        //P(semid);
        printf("I am fa process\n");
        sleep(1);
        //V(semid);
        cnt -= 1;
      }
      waitpid(pid, NULL, 0);
      destorysem(semid);
    }
    return 0;
}

Linux IPC共享内存

共享内存是最快的IPC机制, 因为它不涉及任何的数据传输. 而是采取利用一块公共物理内存映射, 挂载到进程地址空间, 使得多个进程对于同一块物理内存可视的方法实现通信.


但是也存在一定的弊端, 就是无法支持同步, 我们就需要借助其他通信机制手段来同步进程对共享内存的访问


eg : 我 A 进程对于共享内存写入了数据, 需要跟 B 进程通信, 建立同步, 可以这个时候存在一个很大的问题, B 进行如何可以得知 A 进行已经写入了数据了.    此时我们就需要借助semaphore来实现同步.       ---   这一点很重要, 理解为何使用共享内存就需要配合其他通信机制来完成同步, 因为共享内存本身就不支持同步访问. 不同于信号量是本身支持原子操作的.

  • shmget

功能:申请共享内存

shmflag : 9位权限标志位:    我们通常使用  0x666  |  IPC_CREAT | IPC_EXCL

插一个权限介绍:     r : 读权限    w : 可写权限   X : 可执行权限.   所谓的9位权限标识位, 就是针对: 文件拥有者, 文件拥有组人员, 其他人员的访问权限的限制

  • shmat

功能:将物理内存连接,挂载,关联到进程地址空间中去


shmaddr :NULL 自动选取连接地址,         不为NULL, 人为指定到希望挂载的地址上


我们还是填写 NULL


Return Val : 返回一个ptr, 指向共享物理内存的首地址. 虚拟空间中的连接首地址

  • shmdt

功能:解除挂载, 将共享内存和当前进程进行脱离.


shmaddr :  之前 shmat 的 return val, 共享内存映射在虚拟地址空间上的首地址.


Return Val:          success return 0     failure return -1

  • shmctl

功能:对于共享内存IPC进行操作

cmd:  IPC_RMID  删除共享内存

buf 传 NULL 即可

实际案例:利用共享内存实现 父子进程之间的 通信, 父进程向共享内存中写入数据, 子进程在写入时候同步打印写入的数据即可.

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#define ERR_EXIT(m) \
    do { perror(m); exit(EXIT_FAILURE); } while (0);
#define SHMSIZE 1024
//联合结构体
union semun {
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO*/
};
int initsem(key_t key) {
  int semid = -1;
  if (-1 == (semid = semget(key, 1, 0666 | IPC_CREAT))) {
    ERR_EXIT("semid");
  }
  //设置信号量集合中编号为0号的信号量val 为 2
  union semun sem_un;
  sem_un.val = 1;
  if (-1 == semctl(semid, 0, SETVAL, sem_un)) {
    ERR_EXIT("semop set val");
  }
  return semid;
}
void destorysem(int semid) {
  if (-1 == semctl(semid, 0, IPC_RMID)) {
    ERR_EXIT("semctl destorysem");
  }
} 
void P(int semid) {
  struct sembuf op;
  op.sem_num = 0;
  op.sem_op = -1;
  op.sem_flg = SEM_UNDO;
  if (-1 == semop(semid, &op, 1)) {
    ERR_EXIT("semop P");
  }
}
void V(int semid) {
  struct sembuf op;
  op.sem_num = 0;
  op.sem_op = 1;
  op.sem_flg = SEM_UNDO;
  if (-1 == semop(semid, &op, 1)) {
    ERR_EXIT("semop V");
  }
} 
void waitforzero(int semid) {
  struct sembuf op;
  op.sem_num = 0;
  op.sem_op = 0;
  op.sem_flg = SEM_UNDO;
  if (-1 == semop(semid, &op, 1)) {
    ERR_EXIT("semop zero");
  }
} 
int main(int argc, char* argv[]) {
  if (2 != argc) {
    fprintf(stderr, "usage: %s <filepath>", argv[0]);
    exit(EXIT_FAILURE);
  }
  key_t key1 = ftok(argv[1], 'c');
  key_t key2 = ftok(argv[1], 'C');
  //创建信号量集合IPC
  int semid = initsem(key1);
  int shmid = -1;
  pid_t pid = fork();
  if (pid < 0) {
    ERR_EXIT("fork");
  }
  if (pid == 0) {
    //son process
    waitforzero(semid);
    if (-1 == (shmid = shmget(key2, SHMSIZE, 0666 | IPC_CREAT))) {
      ERR_EXIT("shmget");
    }
    void* p = shmat(shmid, NULL, 0);
        if ((void*)-1 == p)
        {
            ERR_EXIT("shmat");
        }
        char buf[SHMSIZE] = {0};
        memcpy(buf, p, SHMSIZE);
        printf("%s\n", buf);
        //卸载共享内存
        if (-1 == shmdt(p))
        {
            ERR_EXIT("shmdt in parent");
        }
    V(semid);//为父进程归还这个资源.
  } else {
    // fa process
    if (-1 == (shmid = shmget(key2, SHMSIZE, 0666 | IPC_CREAT))) {
      ERR_EXIT("shmget");
    }
    void* p;
    p = shmat(shmid, NULL, 0);
    if ((void*)-1 == p)
        {
            ERR_EXIT("shmat");
        }
    char buff[SHMSIZE];
    printf("请输入要传输给子进程的数据\n");
    scanf("%s", buff);
    memcpy(p, buff, strlen(buff));
    P(semid);
    waitpid(pid, NULL, 0);
    //通信完成, 销毁所有IPC
    destorysem(semid);
    //卸载内存, 销毁shmid共享内存
    if (-1 == shmdt(p)) {
      ERR_EXIT("shmdt");
    }
    if (-1 == shmctl(shmid, IPC_RMID, NULL)) {
      ERR_EXIT("shmctl destoryshm");
    }
  }
  return 0;
}
相关文章
|
14天前
|
消息中间件 API Go
微服务的进程间通信(IPC)
微服务的进程间通信(IPC)
30 6
|
16天前
|
消息中间件 Kafka 数据安全/隐私保护
Python IPC实战指南:构建高效稳定的进程间通信桥梁
【8月更文挑战第3天】在软件开发中,高效的进程间通信(IPC)对于构建复杂系统至关重要。本文通过分布式日志处理系统的案例,演示如何利用Python实现可靠的IPC。系统由多个收集日志的进程及中心服务器构成,选用套接字作为IPC机制以支持跨网通信与并发。实战包括定义通信协议、实现日志发送与接收功能,并进行测试部署。此方案不仅展现了Python在IPC领域的强大能力,还为实际应用中的错误处理、数据安全等问题提供了思考方向。
14 1
|
17天前
|
消息中间件 安全 Kafka
Python IPC机制全攻略:让进程间通信变得像呼吸一样自然
【8月更文挑战第2天】在编程领域中,进程间通信(IPC)作为连接独立运行单元的关键桥梁,其重要性不言而喻。本文以Python为例,深入探讨了IPC的各种机制。首先对比了管道与消息队列:管道作为一种基础IPC机制,适用于简单场景;而消息队列通过第三方库如RabbitMQ或Kafka支持更复杂的多生产者多消费者模型,具备高并发处理能力。
16 1
|
18天前
|
Python
惊!Python进程间通信IPC,让你的程序秒变社交达人,信息畅通无阻
【8月更文挑战第1天】在编程世界中,进程间通信(IPC)犹如一场社交舞会,各进程通过IPC机制优雅地交换信息,共同完成复杂任务。IPC就像隐形桥梁,连接并行运行的进程,使它们能跨越边界自由沟通。Python提供了多种IPC机制,如管道、队列、共享内存和套接字等,适应不同需求。例如,使用`multiprocessing.Queue`实现进程间通信,生产者向队列添加数据,消费者取出并处理数据,两者虽独立却能有效协作。IPC打破了进程界限,使得程序能像社交达人般自由交流,构建出高效、灵活的应用。掌握IPC,让程序信息畅通无阻。
16 1
|
18天前
|
安全 开发者 Python
揭秘Python IPC:进程间的秘密对话,让你的系统编程更上一层楼
【8月更文挑战第1天】在系统编程中, 进程间通信 (IPC) 是连接独立进程的关键技术, 提升了系统的并发性和灵活性。Python 提供了丰富的 IPC 机制, 包括管道 (`Pipe`), 队列 (`Queue`), 共享内存 (`Value`, `Array`) 和套接字 (`Sockets`)。这些机制支持不同的应用场景, 如简单的父子进程通信或复杂的分布式系统构建。合理选择 IPC 方法可帮助开发者构建高效、可靠的多进程应用, 但同时也需注意同步和数据一致性等问题。
30 1
|
18天前
|
安全 数据处理 开发者
Python IPC大揭秘:解锁进程间通信新姿势,让你的应用无界连接
【8月更文挑战第1天】在编程领域,Python的进程间通信 (IPC) 架起了不同进程间信息交流的桥梁,使得应用能够跨越边界协同工作。Python提供了丰富的IPC工具,如管道(简单的单向数据通道,适用于父子进程通信)、队列(安全的共享数据结构,支持多进程间的先进先出数据处理)、共享内存(高效的数据共享机制,利用`multiprocessing.Value`和`multiprocessing.Array`实现)、以及套接字(不仅支持网络通信,在本地也能实现进程间通信)。掌握这些机制,开发者就能构建出能够自由穿梭于多个进程的应用,实现更加强大和复杂的功能。
18 1
|
16天前
|
Python
Python IPC深度探索:解锁跨进程通信的无限可能,以管道与队列为翼,让你的应用跨越边界,无缝协作,震撼登场
【8月更文挑战第3天】Python IPC大揭秘:解锁进程间通信新姿势,让你的应用无界连接
13 0
|
16天前
|
消息中间件 数据采集 数据挖掘
庆祝吧!Python IPC让进程间的合作,比团队游戏还默契
【8月更文挑战第3天】在数字化时代,随着软件系统复杂性的提升,Python IPC(进程间通信)技术让多进程协作如同训练有素的电竞战队般默契。通过`multiprocessing`模块中的管道(Pipe),进程可直接、实时地交换数据,犹如配备对讲机,使数据从采集到预处理、分析及展示各阶段流畅衔接,效率倍增且错误减少。此外,IPC还提供消息队列、共享内存、套接字等机制,适应不同场景需求,使进程间的合作如同团队游戏般精准无误,共同构建高效、健壮的软件系统。
21 0
|
16天前
|
消息中间件 网络协议 Python
工具人逆袭!掌握Python IPC,让你的进程从此告别单打独斗
【8月更文挑战第3天】你是否苦恼于Python程序间的“信息孤岛”现象?进程间通信(IPC)技术能助你打破壁垒。IPC是使不同进程共享数据或信号的方法。因全局解释器锁(GIL),多进程配合IPC成为高效处理数据的选择。Python提供管道、消息队列、共享内存等多种IPC手段。例如,管道适合简单父子或兄弟进程通信;套接字不仅限于网络通信,还能实现本地进程间复杂数据交换。掌握IPC,让你的进程协同作战,构建更强大灵活的系统。
9 0
|
17天前
|
消息中间件 数据库 开发者
深度剖析!Python IPC的奥秘,带你走进进程间通信的微观世界
【8月更文挑战第2天】在编程领域中,进程间通信(IPC)是构建复杂软件系统的关键,使数据能在独立进程间流通。本文以Python为例,深入介绍了IPC的基本概念及其实现机制,包括管道、消息队列、套接字等,并重点展示了如何使用Socket进行网络IPC。通过服务器-客户端示例,演示了数据收发过程。此外,还提到了`multiprocessing`模块提供的高级IPC工具,如队列和共享内存,这些工具在实现进程间通信时更加高效。希望通过本文能帮助开发者更好地理解和运用Python IPC技术,构建高效协作的软件系统。
9 0