只知道使用消息队列,而不知为何使用消息队列?

消息队列是什么?

看到队列我们都能想到日常生活中各种排队的场景。而消息队列,顾名思义,就是由一个个消息所排成的队列,这些消息排成队供使用者在需要的时候去使用它们。

消息队列有哪些优点?

  1. 解耦
    使用消息队列时,如果对消息生产和消费的两侧新增业务时,只要你的业务符合生产和消费消息的接口规范,就可以订阅消息,而无需修改原有代码,从而降低了代码之间的耦合度。
  2. 异步
    很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
  3. 削峰
    通过异步处理,可以将短时间内高并发产生的消息存储在消息队列中,从而顶住系统在高峰期所承受的访问压力,而不会因高访问量而崩溃。
  4. 冗余
    消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的"插入-获取-删除"范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你。
    使用完毕。
  5. 扩展性
    消息队列解耦了处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程。
  6. 可恢复性
    系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
  7. 顺序保证
    在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。
  8. 缓冲
    有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。
<think>嗯,用户之前问了一个关于POSIX消息队列的例子,父进程和子进程通过消息队列通信,父进程知道消息数量是固定的。现在用户问如果父进程不知道消息的数量该怎么处理。我需要考虑原来的示例中父进程是循环接收固定次数(MSG_COUNT)的消息,但现在父进程不知道子进程会发多少条消息,所以需要改变接收消息的方式。 首先,父进程需要持续接收消息,直到某种条件触发停止。可能的条件包括:子进程终止、消息队列中没有消息了、或者接收到特定的结束消息。在原来的例子中,父进程等待子进程结束后再关闭队列,所以或许可以结合子进程的终止来判断消息接收结束。 接下来,原来的父进程使用waitpid来等待子进程结束,现在即使子进程结束,父进程可能还有未处理的消息在队列中。因此,可能需要先处理队列中的消息,再关闭资源。或者,在子进程结束时发送一个特殊的终止消息,父进程接收到后停止接收。 另外,消息队列的特性是消息会一直保留直到被读取,即使发送进程已经终止。因此,父进程需要确保在子进程结束后,继续读取队列中的剩余消息,然后再关闭队列。 那如何在父进程中检测子进程是否已经终止呢?原来的代码中,父进程在发送完固定数量的消息后,调用waitpid等待子进程结束。如果父进程不知道消息数量,可能需要一边接收消息,一边检查子进程是否还在运行,或者在子进程结束后继续读取队列中的消息直到队列为空。 可能的解决方案: 1. 父进程在循环中接收消息,同时定期检查子进程的状态。一旦子进程退出,父进程继续读取消息直到队列为空,然后关闭队列。 但如何检查子进程是否还在运行?使用waitpid的非阻塞方式,如果子进程还未退出,则继续接收消息。或者使用信号,当子进程终止时,父进程收到SIGCHLD信号,触发处理。 不过,原来的例子中使用的是阻塞的waitpid,这会导致父进程在循环接收消息时无法同时等待子进程结束。所以可能需要将接收消息和等待子进程结束分开处理,比如使用多线程,或者非阻塞的方式。 另一个方法是,在父进程中,首先循环接收消息,使用mq_receive的非阻塞模式,当队列为空时,检查子进程是否已经退出。如果子进程已退出且队列为空,则退出循环。否则继续等待消息。 具体步骤: - 父进程在接收消息时,将mq_receive设置为非阻塞模式。这样,当没有消息时,mq_receive会立即返回,而不是阻塞等待。 - 修改mq_open的flag,在父进程中以O_RDONLY | O_NONBLOCK方式打开队列?或者使用mq_setattr来设置非阻塞模式? POSIX消息队列的mq_receive的阻塞行为可以通过mq_setattr来设置。默认情况下,队列是阻塞的。如果将标志设置为O_NONBLOCK,那么当队列为空时,mq_receive会立即返回错误,errno设置为EAGAIN。 因此,父进程可以先将队列设置为非阻塞模式,然后在循环中不断尝试接收消息。如果接收到消息则处理,否则检查子进程是否已经退出。如果子进程已退出且队列为空,则退出循环。 不过需要处理的情况是,子进程可能已经退出,但队列中还有未处理的消息。所以父进程需要在子进程退出后,继续处理队列中的消息,直到队列为空。 具体代码调整: 在父进程中,在fork之后,进入接收消息的循环。循环的条件是子进程是否退出,或者队列是否还有消息。可以使用非阻塞的mq_receive,当mq_receive返回-1且errno为EAGAIN时,表示队列暂时无消息,此时检查子进程是否已经退出。如果子进程已退出,再检查一次队列是否还有消息,如果队列为空,则退出循环。否则继续接收。 如何检查子进程是否已经退出?可以使用waitpid的WNOHANG选项,非阻塞地检查子进程状态。 因此,父进程的代码结构大致如下: 在父进程的else块中: // 设置消息队列为非阻塞模式 struct mq_attr attr; mq_getattr(mq, &attr); attr.mq_flags |= O_NONBLOCK; mq_setattr(mq, &attr, NULL); 然后循环: while (1) { char buffer[MAX_MSG_SIZE + 1]; ssize_t bytes = mq_receive(mq, buffer, MAX_MSG_SIZE, NULL); if (bytes >= 0) { buffer[bytes] = '\0'; printf("收到消息: %s\n", buffer); continue; // 继续接收下一条消息 } else if (errno == EAGAIN) { // 队列为空,检查子进程是否退出 int status; pid_t ret = waitpid(pid, &status, WNOHANG); if (ret == pid) { // 子进程已退出,检查队列是否真的没有消息了 // 可能需要再次尝试接收,避免子进程退出前还有消息未处理 // 例如,循环接收直到队列为空 while (1) { bytes = mq_receive(mq, buffer, MAX_MSG_SIZE, NULL); if (bytes >= 0) { buffer[bytes] = '\0'; printf("收到消息: %s\n", buffer); } else if (errno == EAGAIN) { break; // 队列确实空了 } else { perror("mq_receive失败"); break; } } break; // 退出循环 } else if (ret == -1) { perror("waitpid失败"); break; } else { // 子进程未退出,继续等待 sleep(1); // 避免忙等待 } } else { perror("mq_receive失败"); break; } } 然后关闭资源。 这样,父进程会持续接收消息,直到队列为空且子进程已退出。但需要注意,在子进程发送消息后,可能因为父进程在非阻塞模式下快速读取,导致在子进程还在运行时,父进程已经读取了所有消息,然后进入检查子进程状态的循环,此时子进程可能还未退出,父进程需要等待。 或者,子进程可能在发送完所有消息后,关闭队列并退出。父进程在接收到所有消息后,检测到子进程退出,再关闭队列。 另外,原来的示例中子进程发送5条消息后关闭队列并退出。父进程需要确保在子进程退出后,继续处理队列中的所有剩余消息,即使数量未知。 可能的另一个方法是,在子进程发送完所有消息后,发送一个特殊的终止消息,例如内容为"END",父进程接收到该消息后停止接收。但这种方法需要子进程和父进程之间有协议,适用于应用层可以控制消息内容的情况。 但用户的问题可能希望父进程无需知道消息数量或内容,自动检测子进程结束并处理所有消息。因此,结合检测子进程状态和非阻塞接收可能更合适。 现在需要将这些思路转化为代码,修改原来的parent.c,使得父进程在不知道消息数量的情况下,持续接收消息直到子进程结束且队列为空。 可能的代码调整如下: 在父进程中: 1. 设置消息队列为非阻塞模式。 2. 使用循环接收消息,当mq_receive返回EAGAIN时,检查子进程是否已退出。 3. 如果子进程已退出,继续接收直到队列为空。 4. 最后关闭资源。 需要注意,设置非阻塞模式可能需要使用mq_setattr函数。 例如,在父进程打开队列后: mqd_t mq = mq_open(QUEUE_NAME, O_CREAT | O_RDONLY, 0644, &attr); // 错误处理... // 设置为非阻塞模式 struct mq_attr new_attr; if (mq_getattr(mq, &new_attr) == -1) { perror("mq_getattr失败"); exit(EXIT_FAILURE); } new_attr.mq_flags |= O_NONBLOCK; if (mq_setattr(mq, &new_attr, NULL) == -1) { perror("mq_setattr失败"); exit(EXIT_FAILURE); } 然后进入循环: while (1) { char buffer[MAX_MSG_SIZE + 1]; ssize_t bytes = mq_receive(mq, buffer, MAX_MSG_SIZE, NULL); if (bytes >= 0) { buffer[bytes] = '\0'; printf("收到消息: %s\n", buffer); continue; } else if (errno == EAGAIN) { // 队列为空,检查子进程是否已退出 int status; pid_t ret = waitpid(pid, &status, WNOHANG); if (ret == -1) { perror("waitpid失败"); break; } else if (ret == pid) { // 子进程已退出,处理剩余消息 while (1) { bytes = mq_receive(mq, buffer, MAX_MSG_SIZE, NULL); if (bytes >= 0) { buffer[bytes] = '\0'; printf("收到消息: %s\n", buffer); } else if (errno == EAGAIN) { // 队列已空 break; } else { perror("mq_receive失败"); break; } } break; } else { // 子进程仍在运行,等待一段时间再检查 sleep(1); } } else { perror("mq_receive失败"); break; } } 这样,父进程会不断尝试接收消息,当队列为空时检查子进程状态。如果子进程已退出,则处理完剩余消息后退出循环。否则等待一段时间后继续检查。 需要注意的是,这种方法可能引入延迟,因为父进程在队列为空时会sleep(1),可能导致子进程发送的消息不能及时被接收。但如果不使用sleep,会导致CPU占用过高。或者,可以使用更高效的方法,如使用select或poll来等待消息队列的可读事件,但POSIX消息队列的文件描述符可能需要用mq_getfd来获取,但这可能因系统而异,不是所有系统都支持。 另一种方法是使用多线程:一个线程负责接收消息,另一个线程等待子进程退出,然后通知接收线程停止。但这样会增加复杂度,可能超出用户的需求范围。 因此,比较实际的做法是使用非阻塞接收,结合定期检查子进程状态,并适当休眠以避免忙等待。 总结修改后的parent.c代码: 在父进程的接收部分,修改循环结构,使用非阻塞接收,并在队列为空时检查子进程是否退出,处理剩余消息。 同时,原来的父进程在fork之后的else块中执行接收和等待,现在需要调整循环逻辑。 可能还需要处理信号中断等问题,比如mq_receive可能被信号中断,需要检查errno是否为EINTR,但为了简化示例,可能暂时忽略这部分处理。 现在,将上述思路整合到示例代码中,并提供给用户。</think>以下是改进后的实现方案,当父进程**不知道消息数量**时的处理方式: ```c /* parent.c 改进版 */ #include <fcntl.h> #include <sys/stat.h> #include <mqueue.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <errno.h> #define QUEUE_NAME "/demo_queue" #define MAX_MSG_SIZE 1024 int main() { struct mq_attr attr = { .mq_maxmsg = 10, .mq_msgsize = MAX_MSG_SIZE }; mqd_t mq = mq_open(QUEUE_NAME, O_CREAT | O_RDONLY, 0644, &attr); if (mq == (mqd_t)-1) { perror("父进程: mq_open失败"); exit(EXIT_FAILURE); } // 设置为非阻塞模式 struct mq_attr new_attr; mq_getattr(mq, &new_attr); new_attr.mq_flags |= O_NONBLOCK; if (mq_setattr(mq, &new_attr, NULL) == -1) { perror("mq_setattr失败"); exit(EXIT_FAILURE); } pid_t pid = fork(); if (pid == -1) { perror("fork失败"); exit(EXIT_FAILURE); } if (pid == 0) { execlp("./child", "child", NULL); perror("execlp失败"); exit(EXIT_FAILURE); } else { printf("父进程[%d] 开始监听\n", getpid()); int child_exited = 0; char buffer[MAX_MSG_SIZE + 1]; while (1) { ssize_t bytes = mq_receive(mq, buffer, MAX_MSG_SIZE, NULL); if (bytes > 0) { buffer[bytes] = '\0'; printf("收到: %s\n", buffer); continue; } // 检查子进程状态 int status; pid_t ret = waitpid(pid, &status, WNOHANG); if (ret == pid) { // 子进程已退出 child_exited = 1; printf("检测到子进程终止\n"); // 清空剩余消息 while ((bytes = mq_receive(mq, buffer, MAX_MSG_SIZE, NULL)) > 0) { buffer[bytes] = '\0'; printf("收到残留消息: %s\n", buffer); } break; } else if (ret == -1) { perror("waitpid错误"); break; } // 处理错误情况 if (bytes == -1) { if (errno == EAGAIN) { // 无消息时短暂休眠 usleep(100000); // 100ms } else { perror("mq_receive错误"); break; } } } mq_close(mq); mq_unlink(QUEUE_NAME); printf("资源清理完成\n"); } return 0; } ``` **子进程(child.c)保持原逻辑不变**,但可以修改为发送任意数量消息: ```c // child.c 修改消息数量为随机 for (int i = 1; i <= 3 + rand()%5; i++) { // 发送3-7条消息 char msg[MAX_MSG_SIZE]; snprintf(msg, MAX_MSG_SIZE, "Message %d", i); // 发送逻辑保持不变... } ``` **关键改进点分析**: 1. 非阻塞模式设置 ```c new_attr.mq_flags |= O_NONBLOCK; mq_setattr(mq, &new_attr, NULL); ``` 将消息队列设置为非阻塞模式,使`mq_receive`在没有消息时立即返回 2. 双重检测机制 ```c while (1) { // 接收消息... // 检查子进程状态... // 错误处理... } ``` - 主循环持续尝试接收消息 - 每次接收失败后检查子进程状态 - 检测到子进程退出后执行最终清理 3. 残留消息处理 ```c while ((bytes = mq_receive(...)) > 0) { printf("收到残留消息..."); } ``` 在子进程退出后,强制读取所有剩余消息 4. 资源释放保障 ```c mq_close(mq); mq_unlink(QUEUE_NAME); ``` 即使出现异常也确保执行清理操作 **执行流程示意图**: ``` 父进程启动 ├─ 创建消息队列 ├─ 创建子进程 │ └─ 子进程发送N条消息后退出 └─ 父进程进入监听循环 ├─ 成功接收消息 → 立即处理 ├─ 无消息时 → 检查子进程状态 │ ├─ 子进程存活 → 等待100ms后重试 │ └─ 子进程退出 → 处理残留消息 └─ 最终清理资源 ``` **扩展优化建议**: 1. 添加信号处理 ```c signal(SIGCHLD, handler); // 使用信号通知机制优化状态检测 ``` 2. 设置接收超时 ```c struct timespec timeout = {.tv_sec = 1}; mq_receive(..., &timeout); ``` 3. 使用优先级消息 ```c unsigned int prio; mq_receive(..., &prio); // 通过消息优先级识别终止信号 ``` 该方案实现了:消息数量未知时的可靠接收、子进程状态实时检测、资源安全释放三大核心需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值