为了提高程序的执行效率和资源利用率,常常需要使用多进程或多线程。然而,随之而来的问题就是如何保证这些并发执行的进程或线程之间能够正确地同步和通信。本文将以生产者-消费者模型为例,介绍一些相关的技术知识点。
多进程同步与通信的基本概念
进程与线程
进程是操作系统分配资源的基本单位,具有独立的地址空间。线程是进程中的一个实体,是CPU调度和执行的单位,同一进程中的线程共享进程的资源。
同步
同步是确保多个进程在执行时能够协调一致,按照预定的顺序进行操作的机制。
互斥
互斥是同步的一种形式,确保在任何时刻,只有一个进程能够访问特定的资源或执行特定的代码段。
死锁
死锁是多个进程在等待对方释放资源的过程中形成的循环等待状态。
同步机制
互斥锁(Mutex)
互斥锁是一种基本的同步机制,用于防止多个进程同时访问共享资源。当一个进程持有互斥锁时,其他进程必须等待直到互斥锁被释放。
信号量(Semaphore)
信号量是一种计数器,用于控制对共享资源的访问数量。它可以用来控制同时访问共享资源的进程数量。
条件变量(Condition Variable)
条件变量用于进程之间的同步,允许进程在某些条件不满足时挂起,并在条件满足时被唤醒。
屏障(Barrier)
屏障是一种同步机制,用于确保所有参与的进程都到达某个点之后,才继续执行后续的操作。
通信机制
共享内存
共享内存是一种通信机制,允许多个进程访问同一块内存区域,实现数据的直接交换。
管道(Pipe)
管道是一种进程间通信的方式,通常用于父子进程之间的数据传输。
消息队列(Message Queue)
消息队列允许进程发送和接收消息,是一种间接通信的方式。
套接字(Socket)
套接字是一种网络通信机制,支持进程间通过网络进行数据交换。
举例:
为了实现消费者进程之间的同步通信,我们可以使用消息队列来传递消息。当一个消费者进程处理完数据后,它会发送一个消息到消息队列,另一个消费者进程在收到消息后才开始处理数据。
// 消费者进程1
msgsnd(msgid, &msg, sizeof(msg) - sizeof(long), 0);
// 消费者进程2
msgrcv(msgid, &msg, sizeof(msg) - sizeof(long), MSG_ID, 0);
生产者-消费者模型
问题描述
生产者-消费者问题涉及到两个进程:生产者生成数据并将其放入缓冲区,消费者从缓冲区取出数据进行处理。缓冲区的大小是有限的,因此需要同步机制来避免生产者和消费者之间的冲突。
解决方案
- 互斥锁:保护缓冲区的访问,确保在任何时刻只有一个进程能够访问缓冲区。
- 信号量:控制缓冲区的容量,生产者在缓冲区满时等待,消费者在缓冲区空时等待。
- 条件变量:通知消费者有新数据可用,通知生产者缓冲区有空间可用。
实现步骤
1. 初始化
- 创建共享缓冲区。
- 初始化互斥锁、信号量和条件变量。
2. 生产者进程
- 生成数据。
- 通过互斥锁访问缓冲区。
- 如果缓冲区已满,等待或使用信号量控制。
- 将数据放入缓冲区。
- 使用条件变量通知消费者数据已生成。
3. 消费者进程
- 通过互斥锁访问缓冲区。
- 如果缓冲区为空,等待或使用信号量控制。
- 从缓冲区取出数据。
- 使用条件变量通知生产者缓冲区有空间。
4. 同步与通信
- 使用互斥锁确保数据一致性。
- 使用信号量控制缓冲区的使用状态。
- 使用条件变量实现生产者和消费者之间的通知机制。
...
void process1Init(char ** argv);
//void process2Init();
void process3Init();
void signalConfig(sigset_t *mask_set, struct sigaction *act);
void signalReconfig(sigset_t *mask_set, struct sigaction *act);
void semUnlock(int semNum);
void semLock(int semNum);
void semInit();
void shmCreate();
void shmAttach();
void shmDetach();
void shmRemove();
void openFifo(int openingFlag);
void shmSendMessage(char *message, FILE *fp);
void printProcessPID();
FILE *openFile(char *filename);
...
void sigHandlerChildren(int signal) {
if (signal == SIGINT) {
kill(getppid(), SIGUSR1);
}
if (signal == SIGTSTP) {
kill(getppid(), SIGUSR2);
}
if (signal == SIGCONT) {
kill(getppid(), SIGHUP);
}
if (signal == SIGUSR1) {
kill(getppid(), SIGTTIN);
shmDetach();
kill(getpid(), SIGKILL);
}
if (signal == SIGUSR2) {
lock = 1;
char message[5];
iterator--;
read(fifoDescriptor, message, sizeof(message));
}
if (signal == SIGHUP) {
lock = 0;
}
}
void sigHandlerParent(int signal) {
if (signal == SIGUSR1) {
kill(process1PID, SIGUSR1);
kill(process2PID, SIGUSR1);
kill(process3PID, SIGUSR1);
}
if (signal == SIGUSR2) {
kill(process1PID, SIGUSR2);
kill(process2PID, SIGUSR2);
kill(process3PID, SIGUSR2);
}
if (signal == SIGHUP) {
kill(process1PID, SIGHUP);
kill(process2PID, SIGHUP);
kill(process3PID, SIGHUP);
}
if (signal == SIGTTIN) {
close(fifoDescriptor);
unlink(myFIFO);
shmRemove();
semctl(semId, 0, IPC_RMID);
remove(pidsFileName);
kill(getpid(), SIGKILL);
}
}
void signalConfig(sigset_t *mask_set, struct sigaction *act) {
umask(0);
sigfillset(mask_set);
sigdelset(mask_set, SIGUSR1);
sigdelset(mask_set, SIGUSR2);
sigdelset(mask_set, SIGHUP);
sigdelset(mask_set, SIGCONT);
sigdelset(mask_set, SIGINT);
sigdelset(mask_set, SIGTSTP);
sigdelset(mask_set, SIGTTIN);
sigprocmask(SIG_SETMASK, mask_set, NULL);
act->sa_handler = &sigHandlerChildren;
act->sa_mask = *mask_set;
act->sa_flags = 0;
sigaction(SIGTSTP, act, NULL);
sigaction(SIGINT, act, NULL);
sigaction(SIGCONT, act, NULL);
sigaction(SIGUSR1, act, NULL);
sigaction(SIGUSR2, act, NULL);
sigaction(SIGHUP, act, NULL);
}
void signalReconfig(sigset_t *mask_set, struct sigaction *act) {
sigaddset(mask_set, SIGCONT);
sigaddset(mask_set, SIGINT);
sigaddset(mask_set, SIGTSTP);
sigprocmask(SIG_BLOCK, mask_set, NULL);
act->sa_handler = &sigHandlerParent;
act->sa_mask = *mask_set;
act->sa_flags = 0;
sigaction(SIGUSR1, act, NULL);
sigaction(SIGUSR2, act, NULL);
sigaction(SIGHUP, act, NULL);
sigaction(SIGTTIN, act, NULL);
}
void inputParamsCheck(int argc, char **argv) {
if (argc >= 2) {
if (argv[1][0] == '-') {
if (strncmp(argv[1], "-i", 2) == 0)
printf("The program will operate in interactive mode\n");
else if (strncmp(argv[1], "-u", 2) == 0)
printf("The program will fetch characters from /dev/urandom\n");
else if (strncmp(argv[1], "-f", 2) == 0) {
if (argc > 2) {
if (access(argv[2], R_OK) == -1) {
fprintf(stderr, "Unable to open file");
exit(EXIT_FAILURE);
}
} else {
fprintf(stderr, "No file path specified");
exit(EXIT_FAILURE);
}
printf("The program will work in download mode %s\n", argv[2]);
} else {
fprintf(stderr, "Wrong program mode selected\n");
exit(EXIT_FAILURE);
}
} else {
fprintf(stderr, "Invalid program parameters\n");
exit(EXIT_FAILURE);
}
} else {
fprintf(stderr,
"Invalid program parameters\n");
exit(EXIT_FAILURE);
}
}
void init() {
semInit();
shmCreate();
}
void savePIDToFile(){
pidsFileDescriptor = fopen(pidsFileName, "wt");
if(pidsFileDescriptor == NULL){
fprintf(stderr, "Process IDs not saved to file");
}
fprintf(pidsFileDescriptor, "Process%d\n", process1PID);
fprintf(pidsFileDescriptor, "Process%d\n", process2PID);
fprintf(pidsFileDescriptor, "Process%d", process3PID);
fclose(pidsFileDescriptor);
}
int main(int argc, char **argv) {
inputParamsCheck(argc, argv);
sigset_t mask_set;
struct sigaction act;
init();
signalConfig(&mask_set, &act);
if ((process1PID = fork()) == 0) {
process1Init(argv);
return 0;
}
if ((process2PID = fork()) == 0) {
const int CONVERTED_MESSAGE_SIZE = 2 * MAX_MESSAGE_SIZE;
char message[CONVERTED_MESSAGE_SIZE];
char buffer[MAX_MESSAGE_SIZE];
shmAttach();
openFifo(O_WRONLY);
printProcessPID();
iterator = 0;
int convertingIterator = 0;
semUnlock(0);
while (1) {
memset(message, 0, CONVERTED_MESSAGE_SIZE);
memset(buffer, 0, MAX_MESSAGE_SIZE);
while (lock) {}
semLock(1);
strncpy(buffer, shm, MAX_MESSAGE_SIZE);
for (iterator = 0, convertingIterator = 0; iterator < strlen(buffer); iterator++, convertingIterator += 2) {
sprintf((char *) (message + convertingIterator), "%02x", buffer[iterator]);
}
write(fifoDescriptor, message, strlen(message) + 1);
semUnlock(0);
}
return 0;
}
if ((process3PID = fork()) == 0) {
process3Init();
return 0;
}
signalReconfig(&mask_set, &act);
savePIDToFile();
while (1) {
pause();
}
return 0;
}
主要功能是通过进程间通信(IPC)实现字符的读取、转换和写入。程序包含三个子进程,分别负责初始化共享内存和信号量、从文件或/dev/urandom中读取字符并写入共享内存、将共享内存中的字符转换为十六进制格式并通过命名管道发送给第三个进程。
- 首先,程序定义了一些全局变量,如锁、FIFO名称、FIFO描述符、迭代器、共享内存ID、信号量ID等。
inputParamsCheck
函数用于检查命令行参数,根据参数选择程序的工作模式(交互模式、从文件中读取字符或从/dev/urandom中获取字符)。init
函数调用semInit
和shmCreate
函数,分别初始化信号量和共享内存。savePIDToFile
函数将三个进程的PID保存到文件中。main
函数首先调用inputParamsCheck
检查命令行参数,然后调用signalConfig
配置信号处理。接下来,通过fork
创建三个子进程,分别执行process1Init
、process2Init
和process3Init
函数。最后,调用signalReconfig
恢复信号处理,并将三个进程的PID保存到文件中。- 在
process2Init
函数中,程序打开FIFO,将共享内存中的字符转换为十六进制格式并通过FIFO发送给第三个进程。这个过程会一直循环进行,直到程序结束。
so_client:
#define MAX_PROC_NUMBER 3
pid_t procPIDs[MAX_PROC_NUMBER];
FILE *openFile() {
FILE *fp = fopen("/tmp/myPIDs", "r");
if (fp == NULL) {
fprintf(stderr, "Could not open file %s\n", "/tmp/myPIDs");
exit(EXIT_FAILURE);
}
return fp;
}
void parseInput(FILE *fp) {
...
while (getline(&bufferPointer, &len, fp) != -1) {
if (strstr(buffer, stringToFind) != NULL) {
...
}
}
}
int signalInput(int *signalChoice) {
printf("Select Signal to Send: \n");
printf("%d - SIGINT %s\t", 0, "Quit");
printf("%d - SIGTSTP %s\t", 1, "Hold it");
printf("%d - SIGCONT %s\t", 2, "Resume");
printf("Selection: ");
scanf("%d", signalChoice);
if ((*signalChoice != 0&&*signalChoice != 1)&&*signalChoice != 2) {
fprintf(stderr, "Wrong selection \n");
return -1;
}
return 0;
}
int pidInput(int *pidChoice) {
printf("Select Process Number: \n");
for (short i = 0; i < MAX_PROC_NUMBER; i++)
printf("%d - PID: %d \t", i, procPIDs[i]);
printf("Selection: ");
scanf("%d", pidChoice);
if ((*pidChoice != 0&&*pidChoice != 1)&&*pidChoice != 2) {
fprintf(stderr, "Wrong selection \n");
return -1;
}
return 0;
}
void input() {
int pidChoice = -1;
int signalChoice = -1;
while (1) {
if (pidInput(&pidChoice) < 0) continue;
if (signalInput(&signalChoice) < 0) continue;
...
printf("The signal was sent to the process: %d\n", procPIDs[pidChoice]);
}
}
int main(int argc, char **argv) {
FILE *fp = openFile();
parseInput(fp);
input();
fclose(fp);
return 0;
}
主要功能是向指定的进程发送信号。代码首先从文件中读取进程ID,然后根据用户输入的信号类型和进程编号,向相应的进程发送信号。信号类型包括SIGINT(退出)、SIGTSTP(暂停)和SIGCONT(继续)
so_app:
so_client:
If you need the complete source code, please add the WeChat number (c17865354792)
总结
实现多进程同步与通信是一个复杂但至关重要的任务。生产者-消费者模型提供了一个框架,通过使用互斥锁、信号量、条件变量等同步机制,可以有效地实现进程间的协调和数据交换。这些技术不仅适用于生产者-消费者模型,也是解决其他并发问题的基础。
在实际应用中,需要根据具体的应用场景和需求,选择合适的同步和通信机制,以确保系统的稳定性和效率。随着技术的发展,新的同步和通信技术也在不断涌现,为解决并发问题提供了更多的选择和可能性。
We also undertake the development of program requirements here. If necessary, please follow the WeChat official account 【程序猿编码】and contact me