多进程
多进程是什么?怎么使用?带你入门多进程
写在前面
本篇仅作简单介绍,内容适合入门级别,可通过目录查看。重点在于熟悉每一个的经典使用模式
文中相关api就不做过多介绍,不然字数就太多了,可自行去api手册了解
语言不保证严谨且语言组织比较奇怪,内容也不保证完全,欢迎指正错误、补充
一、多进程基础
一个进程占有的空间是虚拟内存(4G),分为两块,用户空间(0-3G)和内核空间(3-4G)
一、目的
1> 实现多任务并发
2> 实现进程间通信,方便数据处理
二、时间片轮转
单个cpu核心工作时,并不能同时处理多个任务,但每次都一个任务干到底并不现实,当某一个任务卡到输入时,程序阻塞了,什么都干不了——时间片轮转的机制就能解决这一现象,给运行队列中的每一个任务分配一定的时间段,这个时间段非常短,时间结束后就继续执行下一个任务,如此循环,这就是时间片轮转。
三、特殊的进程
- 0号进程:这是由linux操作系统启动后运行的第一个进程,也叫空闲进程,当没有其他进程运行时,会运行该进程。是1号进程和2号进程的父进程
- 1号进程:由0号进程创建,完成一些必要的初始化操作,还会收养孤儿进程
- 2号进程:由0号进程创建,完成任务调度问题,也称调度进程
- 孤儿进程:当前进程还在运行时,其父进程退出了
- 僵尸进程:当前进程已经退出,但是其父进程没有为其回收资源
四、linux进程相关指令
1 2 3 4 5 6 7 8 9 10
| ps -ef # 查看进程间关系 ps -ajx # 查看进程状态 ps -aux # 查看进程对CPU和内存占用 top # 动态查看进程相关属性 kill 信号号 进程号 # 发送信号给进程 pidof 进程名 # 查看进程的进程号 jobs -l # 查看停止进程的作业号 bg 作业号 # 实现将停止的进程进入后台运行的状态 fg 作业号 # 实现将后台运行的进程切换到前台运行 ./可执行程序 & # 直接将可执行程序后台运行
|
五、多进程编程的一些基础api
c风格和C风格的多进程差别不大,多进程管理比较底层,本质依赖系统调用,而这些系统调用本身就是C接口,实际大多仍使用C风格。
1 2 3 4
| 进程创建:fork() 进程退出:exit(), _exit() 进程号获取:getpid(), getppid() 进程资源回收:wait(), waitpid()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include <stdio.h>
int main(int argc, const char *argv[]) { pid_t pid = fork(); if(pid > 0) { int num = 5; while(num--) { printf("我是父进程\n"); sleep(1); } wait(NULL); } else if(pid == 0) { int num = 2; while(num--) { printf("我是子进程\n"); sleep(1); } exit(EXIT_SUCCESS); } else { perror("fork error"); return -1; } return 0; }
|
二、进程间通信IPC
每个进程的用户空间时相互独立的,多个进程用户空间的数据交换需要进程间通信
可以使用外部文件进行多个进程之间的数据传递,简单但不是很可靠,可以用来进行少量数据传递
可以利用内核空间来完成数据交换,因为进程的内核空间是共享的,在内核空间创建出一个特殊的区域,一个进程往里面放数据,另一个进程从里面取数据
1 2 3 4 5 6 7 8 9
| 1、内核提供的通信方式(传统的通信方式) 无名管道 有名管道 信号 2、system V提供的通信方式 消息队列 共享内存 信号量(信号灯集) 3、套接字通信:socket 网络通信(跨主机通信)
|
| IPC方式 |
传输速度 |
使用复杂度 |
适用场景 |
| 管道(pipe) |
中等 |
简单 |
父子进程间简单通信 |
| 命名管道(FIFO) |
中等 |
简单 |
无关进程间通信 |
| 消息队列 |
中等 |
中等 |
消息传递,有优先级 |
| 共享内存 |
最快 |
复杂 |
大量数据交换 |
| 信号量 |
快 |
中等 |
进程同步 |
| 信号 |
快 |
简单 |
进程控制,事件通知 |
| 套接字 |
慢 |
复杂 |
网络通信,本地通信 |
一、无名管道
1 2 3 4 5 6
| #include <unistd.h>
int pipe(int fildes[2]); 功能:创建一个无名管道,并返回该管道的两个文件描述符 参数:是一个整型数组,用于返回打开的管道的两端的文件描述符, fildes[0]表示读端 fildes[1]表示写端 返回值:成功返回0,失败返回-1并置位错误码
|
涉及到文件描述符,可使用文件IO——write()、read()
读端存在时:写端最多可写满至64K(64436个字节),之后在write处阻塞
读端不存在时:写端写入会导致管道破裂,之后向内核空间发送SIGPIPE信号,导致退出
写端存在时:读端可读至管道空为止,之后在read处阻塞
写端不存在时:读端可读至管道空为止,之后不会在read处阻塞
经典使用模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/wait.h>
int main(int argc, const char *argv[]) { int pipefd[2]; if(pipe(pipefd) == -1) { perror("pipe error"); return -1; } pid_t pid = fork(); char wbuf[128] = "hello world"; char rbuf[128] = ""; if(pid > 0) { close(pipefd[0]); write(pipefd[1], wbuf, sizeof(wbuf)); close(pipefd[1]); wait(NULL); } else if(pid == 0) { close(pipefd[1]); read(pipefd[0], rbuf, sizeof(rbuf)); printf("收到父进程的数据为:%s\n", rbuf); close(pipefd[0]); exit(EXIT_SUCCESS); } else { perror("fork error"); return -1; } return 0; }
|
二、有名管道
1 2 3 4 5 6 7 8 9 10
| #include <sys/types.h> #include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode); 功能:创建一个管道文件,并存在与文件系统中 参数1:管道文件的名称 参数2:管道文件的权限,内容详见文件IO中open函数的mode参数 返回值:成功返回0,失败返回-1并置位错误码
注意:管道文件被创建后,其他进程就可以进行打开读写操作了,但是,必须要保证当前管道文件的两端都打开后,才能进行读写操作,否则函数会在open处阻塞
|
create.cpp(用于创建有名管道)
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <stdio.h> #include <sys./types.h> #include <sys/stat.h>
int main(int argc, const char *argv[]) { if(mkfifo("./myfifo", 0664) == -1) { perror("mkfifo error"); return -1; } printf("管道创建成功\n"); }
|
send.cpp(用于向管道中写入数据)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include <stdio.h> #include <sys./types.h> #include <sys/stat.h> #include <string.h>
int main(int argc, const char *argv[]) { int wfd = -1; if((wfd = open("./myfifo", O_WRONLY)) == -1) { perror("open error"); return -1; } char wbuf[128] = ""; while(1) { fgets(wbuf, sizeof(wbuf), stdin); wbuf[strlen(wbuf) - 1] = '\0'; write(wfd, wbuf, strlen(wbuf)); if(strcmp(wbuf, "quit") == 0) { break; } } close(wfd); return 0; }
|
recv.cpp(用于从管道中读取数据)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <stdio.h> #include <sys./types.h> #include <sys/stat.h> #include <string.h>
int main(int argc, const char *argv[]) { int rfd = -1; if((rfd = open("./myfifo", O_RDONLY)) == -1) { perror("open error"); return -1; } char rbuf[128] = ""; while(1) { read(rfd, rbuf, sizeof(rbuf)); if(strcmp(rbuf, "quit") == 0) { break; } printf("收到数据:%s\n", rbuf); } close(rfd); unlink(myfifo); return 0; }
|
三、信号
信号可用字面义来理解,就是起到通知进程的作用。A进程发出通知给B进程,B进程收到通知就会执行相关处理,就像人一样
两个特殊信号:SIGKILL和SIGSTOP,这两个信号既不能被捕获,也不能被忽略
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler); 功能:将信号与信号处理方式绑定到一起 参数1:要处理的信号 参数2:处理方式 SIG_IGN:忽略 SIG_DFL:默认,一般信号的默认操作都是杀死进程 typedef void (*sighandler_t)(int):用户自定义的函数 返回值:成功返回处理方式的起始地址,失败返回SIG_ERR并置位错误码
注意:只要程序与信号绑定一次,后续但凡程序收到该信号,对应的处理方式就会立即响应
|
使用信号对僵尸进程回收
原理:当子进程退出后,会向父进程发送一个SIGCHLD的信号,只需要捕获该信号即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #include <myhead.h>
void handler(int signo) { if(signo == SIGCHLD) { while(waitpid(-1, NULL, WNOHANG) > 0); } }
int main(int argc, const char *argv[]) { if(signal(SIGCHLD, handler) == SIG_ERR) { perror("signal error"); return -1; } for(int i = 0; i < 10; i++) { if(fork() == 0) { exit(EXIT_SUCCESS); } } while(1); return 0; }
|
向进程发送信号,进程收到信号后就执行相关处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <signal.h>
int kill(pid_t pid, int sig); 功能:向指定进程或进程组发送信号 参数1:进程号或进程组号 >0:表示向执行进程发送信号 =0:向当前进程所在的进程组中的所有进程发送信号 =-1:向所有进程发送信号 <-1:向指定进程组发送信号,进程组的ID号为给定pid的绝对值 参数2:要发送的信号 返回值:成功返回0,失败返回-1并置位错误码
#include <signal.h>
int raise(int sig); 功能:向自己发送信号 参数:要发送的信号 返回值:成功返回0,失败返回非0数组
|
四、system V提供的进程间通信
对于上面提到的管道通信,只能实现单向通信,而信号通信也只能起到通知的效果,
消息队列、共享内存、信号量(信号灯集)
1 2 3 4 5 6
| 相关指令: ipcs 可以查看所有的信息(消息队列、共享内存、信号量) ipcs -q:可以查看消息队列的信息 ipcs -m:可以查看共享内存的信息 ipcs -s:可以查看信号量的信息 ipcrm -q/m/s ID :可以删除指定ID的IPC对象
|
即使程序已经结束,但容器依然存在,除非手动删除
五、消息队列
原理:内核空间一个消息队列容器,每个元素包含消息类型和消息正文,根据类型存取消息即可
消息取出来后就会从消息队列中移除
消息队列大小:16K
多个进程,使用相同的key值打开的是同一个消息队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> 1、创建key值 key_t ftok(const char *pathname, int proj_id);
2、通过key值,创建消息队列 int msgget(key_t key, int msgflg);
3、向消息队列中存放数据 int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
4、从消息队列中取消息 ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
5、销毁消息队列 int msgctl(int msqid, int cmd, struct msqid_ds *buf);
|
send.cpp(发送端)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h>
#define MSGSIZE (sizeof(struct msgBuf) - sizeof(long))
struct msgBuf { long mtype; char mtext[1024]; };
int main(int argc, const char *argv[]) { key_t key = ftok("/", 'k'); if(key == -1) { perror("ftok error"); return -1; } printf("key = %#x\n", key); int msqid = -1; if((msqid = msgget(key, IPC_CREAT | 0664)) == -1) { perror("msgget error"); return -1; } printf("msqid = %d\n", msqid); struct msgBuf buf; while(1) { printf("输入消息的类型:"); scanf("%ld", &buf.mtype); getchar(); printf("输入消息正文:"); fgets(buf.mtext, MSGSIZE, stdin); buf.mtext[strlen(buf.mtext) - 1] = '\0'; msgsnd(msqid, &buf, MSGSIZE, 0); printf("消息存放成功\n"); if(strcmp(buf.mtext, "quit") == 0) { break; } } return 0; }
|
recv.cpp(接收端)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h>
#define MSGSIZE (sizeof(struct msgBuf) - sizeof(long))
struct msgBuf { long mtype; char mtext[1024]; };
int main(int argc, const char *argv[]) { key_t key = ftok("/", 'k'); if(key == -1) { perror("ftok error"); return -1; } printf("key = %#x\n", key); int msqid = -1; if((msqid = msgget(key, IPC_CREAT | 0664)) == -1) { perror("msgget error"); return -1; } printf("msqid = %d\n", msqid); struct msgBuf buf; while(1) { printf("输入要接收的消息类型:"); scanf("%ld", &buf.mtype); if(msgrcv(msqid, &buf, MSGSIZE, buf.mtype, 0) == -1) { perror("msgrcv error"); break; } printf("消息读取成功:%s\n", buf.mtext); if(strcmp(buf.mtext, "quit") == 0) { break; } } if(msgctl(msqid, IPC_RMID, NULL) == -1) { perror("msgctl IPC_RMID error"); return -1; } return 0; }
|
六、共享内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h>
1、创建key值 key_t ftok(const char *pathname, int proj_id);
2、通过key值创建共享内存段 int shmget(key_t key, size_t size, int shmflg);
3、将共享内存段的地址映射到用户空间 void *shmat(int shmid, const void *shmaddr, int shmflg);
4、释放共享内存的映射关系 int shmdt(const void *shmaddr);
5、共享内存的控制函数 int shmctl(int shmid, int cmd, struct shmid_ds *buf);
|
send.cpp(发送端)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h>
#define PAGE_SIZE 4096
int main(int argc, const char *argv[]) { key_t key = ftok("/", 'k'); if(key == -1) { perror("ftok error"); return -1; } int shmid = -1; if((shmid = shmget(key, PAGE_SIZE, IPC_CREAT | 0644)) == -1) { perror("shmget error"); return -1; } printf("shmid = %d\n", shmid); char *addr = (char *)shmat(shmid, NULL, 0); if(addr == (void *)-1) { perror("shmat error"); return -1; } printf("addr = %p\n", addr); while(1) { printf("请输入>>>"); fgets(addr, PAGE_SIZE, stdin); addr[strlen(addr) - 1] = '\0'; if(strcmp(addr, "quit") == 0) { break; } } if(shmdt(addr) == -1) { perror("shmdt error"); return -1; } return 0; }
|
recv.cpp(接收端)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h>
#define PAGE_SIZE 4096
int main(int argc, const char *argv[]) { key_t key = ftok("/", 'k'); if(key == -1) { perror("ftok error"); return -1; } int shmid = -1; if((shmid = shmget(key, PAGE_SIZE, IPC_CREAT | 0644)) == -1) { perror("shmget error"); return -1; } char *addr = (char *)shmat(shmid, NULL, 0); if(addr == (void*)-1) { perror("shmat error"); return -1; } printf("addr = %p\n", addr); while(1) { sleep(2); printf("读取到消息为:%s\n", addr); if(strcmp(addr, "quit") == 0) { break; } } if(shmdt(addr) == -1) { perror("shmdt error"); return -1; } if(shmctl(shmid, IPC_RMID, NULL) == -1) { perror("shmctl IPC_RMID error"); return -1; } printf("成功删除共享内存段\n"); return 0; }
|
共享内存的竞态问题
- 上面的示例可以看出,共享内存就是对一块可以共享的内存空间进行操作。如果只有两个进程还好,要是有多个进程同时使用共享内存,就会发生竞态问题,什么意思呢?
- 竞态1:假设现在有三个进程ABC,A进程与B进程说悄悄话,但在这根本没有“悄悄”可言,因为C进程是可以随时听到悄悄话的,而A进程和B进程不想让C进程知道,C进程也不想知道。
- 竞态2:A进程和B进程正在通信,但是C进程突然向共享内存中存放数据,那数据就变了呀,A进程和B进程之间的通信就被扰乱了
- 以上两种情况供大家理解,说得不太准确,但相信大家也理解到意思了
- 知道问题了,那如何解决呢?上面我加粗了两个词语,“随时听到悄悄话”和“突然向共享内存中存放数据”就是C进程在执行程序,那就很好解决了,保证顺序性即可,那就需要接下来的信号灯集辅助了
七、信号灯集
信号灯集是一个比较形象的名称,信号灯集可以给每个进程绑定信号量,执行进程需要消耗信号量资源,执行完后可以释放别的信号量,这样别的进程就有信号量来消耗了,可自定义顺序性。那么,如果信号量>0代表发光,那就是一个“信号灯”了。
简单来说,每一个进程都是:申请信号量资源–>执行程序–>释放别的信号量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <errno.h>
1、创建key值 key_t ftok(const char *pathname, int proj_id);
2、通过key值创建信号量集 int semget(key_t key, int nsems, int semflg);
3、关于信号量集的操作:P(申请资源)V(释放资源) int semop(int semid, struct sembuf *sops, size_t nsops);
4、关于信号量集的控制函数 int semctl(int semid, int semnum, int cmd, ...);
|
信号灯集二次封装函数
sem.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #ifndef _SEM_H_ #define _SEM_H_
int create_sem(int semcount);
int P(int semid, int semno);
int V(int semid, int semno);
int delete_sem(int semid);
#endif
|
sem.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
| #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <errno.h>
union semun { int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf; }
int init_sem(int semid, int semno) { int val = -1; printf("请输入第%d个信号量的初始值:", semno); scanf("%d", &val); getchar(); union semun us; us.val = val; if(semctl(semid, semno, SETVAL, us) == -1) { perror("semctl SETVAL error"); return -1; } return 0; }
int create_sem(int semcount) { key_t key = ftok("/", 'k'); if(key == -1) { perror("ftok error"); return -1; } int semid = -1; if((semid = semget(key, semcount, IPC_CREAT | IPC_EXCL | 0664)) == -1) { if(errno == EEXIST) { semid = semget(key, semcount, IPC_CREAT | 0664); return semid; } perror("semget error"); return -1; } for(int i = 0; i < semcount; i++) { init_sem(semid, i); } return semid; }
int P(int semid, int semno) { struct sembuf buf; buf.sem_num = semno; buf.sem_op = -1; buf.sem_flg = 0; if(semop(semid, &buf, 1) == -1) { perror("semop P error"); return -1; } return 0; }
int V(int semid, int semno) { struct sembuf buf; buf.sem_num = semno; buf.sem_op = 1; buf.sem_flg = 0; if(semop(semid, &buf, 1) == -1) { perror("semop V error"); return -1; } return 0; }
int delete_sem(int semid) { if(semctl(semid, 0, IPC_RMID) == -1) { perror("semctl IPC_RMID error"); return -1; } return 0; }
|
看代码长度就知道为什么要二次封装了,要是重复写这些代码,得累死
接下来就融合共享内存和信号量集
shmsnd.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| #include "sem.h"
#define PAGE_SIZE 4096
int main(int argc, const char *argv[]) { int semid = create_sem(2); key_t key = ftok("/", 'k'); if(key == -1) { perror("ktok error"); return -1; } int shmid = -1; if((shmid = shmget(key, PAGE_SIZE, IPC_CREAT | 0644)) == -1) { perror("shmget error"); return -1; } printf("shmid = %d\n", shmid); char *addr = (char *)shmat(shmid, NULL, 0); if(addr == (void *)-1) { perror("shmat error"); return -1; } printf("addr = %p\n", addr); while(1) { P(semid, 0); printf("请输入>>>"); fgets(addr, PAGE_SIZE, stdin); addr[strlen(addr) - 1] = '\0'; V(semid, 1); if(strcmp(addr, "quit") == 0) { break; } } if(shmdt(addr) == -1) { perror("shmdt error"); return -1; } return 0; }
|
shmrcv.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h>
#define PAGE_SIZE 4096
int main(int argc, const char *argv[]) { int semid = create_sem(2); key_t key = ftok("/", 'k'); if(key == -1) { perror("ftok error"); return -1; } int shmid = -1; if((shmid = shmget(key, PAGE_SIZE, IPC_CREAT | 0644)) == -1) { perror("shmget error"); return -1; } char *addr = (char *)shmat(shmid, NULL, 0); if(addr == (void*)-1) { perror("shmat error"); return -1; } printf("addr = %p\n", addr); while(1) { P(semid, 1); printf("读取到消息为:%s\n", addr); if(strcmp(addr, "quit") == 0) { break; } V(semid, 0); } if(shmdt(addr) == -1) { perror("shmdt error"); return -1; } if(shmctl(shmid, IPC_RMID, NULL) == -1) { perror("shmctl IPC_RMID error"); return -1; } printf("成功删除共享内存段\n"); delete_sem(semid); return 0; }
|
注意:
- 信号量集完成多个进程间的同步问题,一般不用于通信,用于辅助
- 信号量集本质时维护了多个val值,根据val值来判断是否阻塞,控制进程的执行
- 信号量为0,则不会再减少了,该进程会在此阻塞
三、总结
通信方式的选择
在使用不同的IPC(进程间通信)方式时,需要考虑它们的性能差异。
- 管道(Pipe):
传输速度一般,但操作简单,适合小规模数据传输。
- 消息队列(Message Queue):
传输速度中等,可靠性好,适合异步通信场景。
- 共享内存(Shared Memory):
传输速度最快,但需要处理同步问题,适合大规模数据传输。
- 信号量(Semaphore):
主要用于进程间同步,传输速度较慢。
一般来说,对于小规模数据传输,管道是最简单高效的选择 ;
对于大数据量或实时性要求高的场景,共享内存是最佳方案 ;
而消息队列则适用于异步通信的场景。根据特点选择适合的IPC方式
延申:实现并发还可以使用多线程,跨主机进程通信可以使用网络编程中的socket套接字
到这里,相信你已经大致了解了多进程是什么,以及简单的应用,希望大家喜欢
: )