C语言实现 操作系统 经典的进程同步问题(2)
哲学家进餐问题
哲学家进餐问题是一个经典的同步问题,涉及多个哲学家试图同时用餐,但每个哲学家左右两边只有一把叉子。为了避免死锁和饥饿,可以使用记录型信号量(也称为计数信号量)来管理叉子的使用。
1、利用记录型信号量解决哲学家进餐问题
(1)信号量初始化:使用 sem_init
初始化每把叉子的信号量,初始值为1,表示可用。
哲学家线程:每个哲学家线程尝试拿起左边的叉子(sem_trywait
),如果失败则继续思考。如果成功拿起左边的叉子,再尝试拿起右边的叉子。如果失败,则放下左边的叉子并继续思考。如果成功拿起两把叉子,则哲学家开始吃饭,吃完后放下两把叉子。
输出控制:使用互斥锁 print_mutex
控制输出顺序,避免输出混乱。
线程创建和等待:创建哲学家线程,并使用 pthread_join
等待所有线程完成(实际上这是一个无限循环程序,所以不会真正退出)。
资源清理:销毁信号量和互斥锁。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h> #define NUM_PHILOSOPHERS 5
#define THINK_TIME 1
#define EAT_TIME 2 sem_t forks[NUM_PHILOSOPHERS]; // 每把叉子一个信号量
pthread_mutex_t print_mutex; // 控制输出顺序的互斥锁 void *philosopher(void *arg) { int id = *((int *)arg); int left_fork = id; int right_fork = (id + 1) % NUM_PHILOSOPHERS; while (1) { // 思考 printf("Philosopher %d is thinking.\n", id); sleep(THINK_TIME); // 尝试拿起左边的叉子 if (sem_trywait(&forks[left_fork]) == -1) { // 拿起左边的叉子失败,继续思考 continue; } // 尝试拿起右边的叉子 if (sem_trywait(&forks[right_fork]) == -1) { // 拿起右边的叉子失败,放下左边的叉子并继续思考 sem_post(&forks[left_fork]); continue; } // 拿起两把叉子,开始吃饭 printf("Philosopher %d is eating.\n", id); sleep(EAT_TIME); printf("Philosopher %d has finished eating.\n", id); // 放下叉子 sem_post(&forks[left_fork]); sem_post(&forks[right_fork]); } pthread_exit(NULL);
} int main() { pthread_t threads[NUM_PHILOSOPHERS]; int ids[NUM_PHILOSOPHERS]; // 初始化信号量,每把叉子的初始值为1,表示可用 for (int i = 0; i < NUM_PHILOSOPHERS; i++) { sem_init(&forks[i], 0, 1); } // 初始化互斥锁,用于控制输出顺序 pthread_mutex_init(&print_mutex, NULL); // 创建哲学家线程 for (int i = 0; i < NUM_PHILOSOPHERS; i++) { ids[i] = i; pthread_create(&threads[i], NULL, philosopher, &ids[i]); } // 等待所有线程完成(实际上,这是一个无限循环程序,所以这里不会退出) for (int i = 0; i < NUM_PHILOSOPHERS; i++) { pthread_join(threads[i], NULL); } // 销毁信号量和互斥锁 for (int i = 0; i < NUM_PHILOSOPHERS; i++) { sem_destroy(&forks[i]); } pthread_mutex_destroy(&print_mutex); return 0;
}
注意:
sem_trywait
是非阻塞的,如果信号量的值大于0,则减一并返回,否则立即返回-1并设置errno为EAGAIN。- 使用
pthread_join
等待线程实际上在这个例子中是不必要的,因为哲学家线程是无限循环的。如果要终止程序,可以添加额外的控制逻辑。
(2)init_random()
:初始化随机数生成器。
think(int philosopherNumber)
:模拟哲学家思考的过程,打印消息并随机睡眠一段时间。
eat(int philosopherNumber)
:模拟哲学家吃饭的过程,打印消息并随机睡眠一段时间。
*philosopher(void *arg)
:哲学家的线程函数,包含无限循环,不断执行思考和吃饭的过程。在拿起叉子前使用互斥锁保护,成功后释放互斥锁,吃饭后放下叉子。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h> // 包含sleep函数所需的头文件
#include <time.h> #define PHILOSOPHERS_COUNT 5 sem_t forks[PHILOSOPHERS_COUNT];
sem_t mutex; // 初始化随机数生成器
void init_random() { srand(time(NULL));
} void think(int philosopherNumber) { printf("Philosopher %d is thinking.\n", philosopherNumber); // 模拟思考时间 int sleepTime = rand() % 5 + 1; sleep(sleepTime);
} void eat(int philosopherNumber) { printf("Philosopher %d is eating.\n", philosopherNumber); // 模拟吃饭时间 int sleepTime = rand() % 5 + 1; sleep(sleepTime);
} void *philosopher(void *arg) { int philosopherNumber = *((int *)arg); while (1) { // 注意:这里的无限循环可能导致程序永远不退出 think(philosopherNumber); // 使用互斥锁保护对叉子的访问 sem_wait(&mutex); // 尝试拿起左右两边的叉子 int leftFork = philosopherNumber; int rightFork = (philosopherNumber + 1) % PHILOSOPHERS_COUNT; sem_wait(&forks[leftFork]); sem_wait(&forks[rightFork]); // 释放互斥锁,因为已经成功拿起了两把叉子 sem_post(&mutex); eat(philosopherNumber); // 放下叉子 sem_post(&forks[leftFork]); sem_post(&forks[rightFork]); } return NULL;
} int main() { pthread_t philosophers[PHILOSOPHERS_COUNT]; int philosopherNumbers[PHILOSOPHERS_COUNT]; // 初始化随机数生成器 init_random(); // 初始化信号量 sem_init(&mutex, 0, 1); for (int i = 0; i < PHILOSOPHERS_COUNT; i++) { sem_init(&forks[i], 0, 1); } // 创建哲学家线程 for (int i = 0; i < PHILOSOPHERS_COUNT; i++) { philosopherNumbers[i] = i; pthread_create(&philosophers[i], NULL, philosopher, &philosopherNumbers[i]); } // 注意:这里使用pthread_join会导致程序永远等待,因为哲学家线程是无限循环的 // 如果需要程序退出,应该实现某种退出机制,比如使用全局变量和条件变量 for (int i = 0; i < PHILOSOPHERS_COUNT; i++) { pthread_join(philosophers[i], NULL); } // 清理资源(在实际应用中,应该在确保所有线程都正确退出后再进行) for (int i = 0; i < PHILOSOPHERS_COUNT; i++) { sem_destroy(&forks[i]); } sem_destroy(&mutex); // 注意:由于使用了无限循环,下面的代码将不会被执行 // 这里只是为了展示如何清理资源 // 在实际应用中,应在程序退出前确保所有线程都正确终止 return 0;
}
注意:由于while (1)
循环的存在,pthread_join
和信号量销毁的代码在实际应用中不会被执行。在实际应用中,您需要实现一种机制来安全地终止线程并清理资源。这通常涉及到使用全局变量、条件变量或其他线程间通信机制来协调线程的终止。
2、利用AND信号量机制解决哲学家进餐问题
(1)AND信号量要求线程(在本例中为哲学家)同时获取多个资源(叉子)才能继续执行。然而,标准的POSIX信号量API并不直接支持AND信号量,但我们可以通过使用互斥锁(mutex)和计数信号量(counting semaphore)的组合来模拟这种机制。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdbool.h>
#include <time.h>#define PHILOSOPHERS_COUNT 5
#define THINK_TIME 1 // 思考时间(秒)
#define EAT_TIME 1 // 吃饭时间(秒)sem_t forks[PHILOSOPHERS_COUNT]; // 每只叉子一个信号量
pthread_mutex_t mutex; // 互斥锁,用于保护对退出标志的访问
bool should_exit = false; // 退出标志// 初始化随机数生成器(虽然在这个示例中未直接使用 rand,但保留以备将来需要)
void init_random() {srand(time(NULL));
}// 哲学家线程函数
void *philosopher(void *arg) {int philosopher_number = *((int *)arg);while (!should_exit || /* 另一个条件,用于确保在退出前完成当前循环 */(should_exit && (philosopher_number % 2 == 0 || /* 可选的:让偶数编号的哲学家先完成 */(sem_trywait(&forks[philosopher_number]) == 0 && sem_trywait(&forks[(philosopher_number + 1) % PHILOSOPHERS_COUNT]) == 0)))) {// 思考printf("Philosopher %d is thinking.\n", philosopher_number);sleep(THINK_TIME);// 尝试拿起左右两边的叉子(模拟 AND 信号量)pthread_mutex_lock(&mutex); // 保护对叉子信号量的操作if (sem_trywait(&forks[philosopher_number]) == 0 &&sem_trywait(&forks[(philosopher_number + 1) % PHILOSOPHERS_COUNT]) == 0) {// 成功拿起两把叉子pthread_mutex_unlock(&mutex); // 释放互斥锁,因为已经成功拿起了叉子// 吃饭printf("Philosopher %d is eating.\n", philosopher_number);sleep(EAT_TIME);// 放下叉子sem_post(&forks[philosopher_number]);sem_post(&forks[(philosopher_number + 1) % PHILOSOPHERS_COUNT]);} else {// 未能同时拿起两把叉子,释放互斥锁并继续思考pthread_mutex_unlock(&mutex);}// 检查退出标志pthread_mutex_lock(&mutex);if (should_exit) {pthread_mutex_unlock(&mutex);break; // 直接跳出循环}pthread_mutex_unlock(&mutex);}return NULL;
}int main() {pthread_t philosophers[PHILOSOPHERS_COUNT];int philosopher_numbers[PHILOSOPHERS_COUNT];// 初始化随机数生成器(虽然在这个示例中未直接使用)init_random();// 初始化信号量和互斥锁pthread_mutex_init(&mutex, NULL);for (int i = 0; i < PHILOSOPHERS_COUNT; i++) {sem_init(&forks[i], 0, 1); // 初始值为 1,表示叉子可用}// 创建哲学家线程for (int i = 0; i < PHILOSOPHERS_COUNT; i++) {philosopher_numbers[i] = i;pthread_create(&philosophers[i], NULL, philosopher, &philosopher_numbers[i]);}// 让哲学家们运行一段时间,然后设置退出标志sleep(10); // 这里只是为了演示,实际中可能使用其他条件来触发退出pthread_mutex_lock(&mutex);should_exit = true;pthread_mutex_unlock(&mutex);// 等待哲学家线程退出for (int i = 0; i < PHILOSOPHERS_COUNT; i++) {pthread_join(philosophers[i], NULL);}// 清理资源for (int i = 0; i < PHILOSOPHERS_COUNT; i++) {sem_destroy(&forks[i]);}pthread_mutex_destroy(&mutex);return 0;
}
(2)sem_t mutex;
:用于保护对共享资源(如state
数组和meals_eaten
数组)的访问。
sem_t s[N];
:表示左右叉子的可用性。如果s[i]
为0,则第i
位哲学家的右叉子(或第(i-1)%N
位哲学家的左叉子)不可用。
sem_t s_try[N];
:用于在哲学家尝试获取叉子时同步。
int state[N];
:表示每位哲学家的当前状态(思考、饥饿、就餐)。
int meals_eaten[N];
:记录每位哲学家吃过的饭的数量。
pthread_cond_t cond[N];
:条件变量,用于在哲学家无法获取叉子时等待
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdbool.h>#define N 5
#define THINKING 0
#define HUNGRY 1
#define EATING 2
#define MAX_MEALS 10 sem_t mutex;
sem_t s[N];
sem_t s_try[N];
int state[N];
int meals_eaten[N];pthread_cond_t cond[N];void *philosopher(void *num);
void take_forks(int);
void put_forks(int);
void test(int);void debug_print(int philosopher_num, const char *message) {printf("Philosopher %d: %s\n", philosopher_num, message);
}int main() {pthread_t thread_id[N];sem_init(&mutex, 0, 1);for (int i = 0; i < N; i++) {sem_init(&s[i], 0, 0);sem_init(&s_try[i], 0, 0);meals_eaten[i] = 0;pthread_cond_init(&cond[i], NULL);}for (int i = 0; i < N; i++) {pthread_create(&thread_id[i], NULL, philosopher, (void *)(long long)i);}for (int i = 0; i < N; i++) {pthread_join(thread_id[i], NULL);}return 0;
}void *philosopher(void *num) {long long int i = (long long int)(num);for (int meal = 0; meal < MAX_MEALS; meal++) {// 思考{sem_wait(&mutex);state[i] = THINKING;sem_post(&mutex);debug_print(i, "Thinking");}// 尝试获取叉子take_forks((int)i);// 吃饭{sem_wait(&mutex);state[i] = EATING;sem_post(&mutex);debug_print(i, "Eating");}// 放下叉子put_forks((int)i);}return NULL;
}void take_forks(int i) {sem_wait(&mutex);state[i] = HUNGRY;debug_print(i, "Hungry");test(i);if (state[i]!= EATING) {pthread_cond_wait(&cond[i], &mutex);}sem_post(&mutex);sem_wait(&s_try[i]);sem_wait(&s[i]);
}void put_forks(int i) {sem_wait(&mutex);state[i] = THINKING;debug_print(i, "Put down forks");meals_eaten[i]++;test((i + 4) % N);test((i + 1) % N);sem_post(&mutex);sem_post(&s[(i + 4) % N]);sem_post(&s[(i + 1) % N]);pthread_cond_signal(&cond[(i + 4) % N]);pthread_cond_signal(&cond[(i + 1) % N]);
}void test(int i) {sem_wait(&mutex);if (state[i] == HUNGRY && state[(i + 4) % N]!= EATING && state[(i + 1) % N]!= EATING) {state[i] = EATING;sem_post(&s_try[i]);sem_post(&s[i]);pthread_cond_signal(&cond[i]);}sem_post(&mutex);
}
注意:
- 代码中使用了信号量和条件变量来同步对共享资源的访问,以避免竞态条件和死锁。
- 通过
mutex
信号量保护对state
数组的访问,确保在检查和修改哲学家状态时不会发生数据竞争。 - 通过
s[i]
和s_try[i]
信号量以及条件变量cond[i]
来协调哲学家获取和释放叉子的过程。 - 代码中的
% N
操作确保了索引在有效范围内循环。
相关文章:
C语言实现 操作系统 经典的进程同步问题(2)
哲学家进餐问题 哲学家进餐问题是一个经典的同步问题,涉及多个哲学家试图同时用餐,但每个哲学家左右两边只有一把叉子。为了避免死锁和饥饿,可以使用记录型信号量(也称为计数信号量)来管理叉子的使用。 1、利用记录型…...

有效的字母异位词【字符串哈希】
题目 题解: 1.排序: #include<algorithm>class Solution{public:bool isAnagram(string s,string t){sort(s.begin(),s.end());sort(t.begin(),t.end());return st;} } 时间复杂度O(nlogn) 2.哈希表 #include<algorithm>int hash1[100]; …...
如何选择与运用工具提升工作效率的秘密指南
一、引言 ---- 在当今这个信息爆炸的时代,编程工具的选择对于开发者的工作效率至关重要。从智能的代码编辑器到强大的版本控制工具,再到那些能让我们事半功倍的自动化脚本,每一款工具都有其独特的优势和价值。那么,哪款编程工具…...

Spring系列 AOP实现过程
文章目录 实现原理EnableAspectJAutoProxyAnnotationAwareAspectJAutoProxyCreator 代理创建过程wrapIfNecessarygetAdvicesAndAdvisorsForBeanfindCandidateAdvisorsfindAdvisorsThatCanApply createProxy AspectJ注解处理代理调用过程 实现原理 本文源码基于spring-aop-5.3.…...

C语言 getchar 函数完全解析:掌握字符输入的关键
前言 在C语言中,getchar 是一个非常实用的函数,用于从标准输入流(通常是键盘)读取单个字符。这对于处理文本输入非常有用,尤其是在需要逐个字符处理的情况下。本文将深入探讨 getchar 函数的用法和特点,并…...
Docker安装mysql8并配置主从复制
1. 安装mysql8 1.1 新增挂载文件 # 新增mysql挂载文件夹 mkdir -p /root/docker/mysql/m01/log mkdir -p /root/docker/mysql/m01/data mkdir -p /root/docker/mysql/m01/conf1.2 新增mysql配置文件 # 新增mysql配置文件 cd /root/docker/mysql/m01/conf vim my.cnf # 下面是…...

快手:数据库升级实践,实现PB级数据的高效管理|OceanBase案例
本文作者:胡玉龙,快手技术专家 快手在较初期采用了OceanBase 3.1版本成功替换了多个核心业务、数百套的MySQL集群。至2023年,快手的数据量已突破800TB大关,其中最大集群的数据量更是达到了数百TB级别。为此,快手将数据…...

基于Node.js+Express+MySQL+VUE实现的计算机毕业设计共享单车管理网站
单车信息选择骑行 骑行状态留言公告/springboot/javaWEB/J2EE/MYSQL数据库/vue前后分离小程序 目录 功能图 界面展示 开发目标 开发背景意义 开发意义 开发目的 项目概述 技术选型与理由 系统设计与功能实现 项目可执行性分析 系统架构需求 性能需…...
人工智能辅助的神经康复
人工智能辅助的神经康复是通过应用人工智能(AI)技术来改善神经系统损伤患者的康复过程。此领域结合了深度学习、数据分析和机器人技术,旨在提升康复效果、个性化治疗方案和监测进展。以下是该领域的关键组成部分和应用: 1. 康复评…...
KKT实际运用 -MATLAB
FMINCON函数可以很方便的求出:fun:目标函数,即需要最小化的函数,输入参数为向量x,输出为标量f(x)。x0:初始点,即求解过程的起始点,可以是标量、向量或矩阵。A和b:线性不等…...

php在线相册
1、将静态页面效果完成 解压到www里 整个数据 暂时是错误的 建立连接密码为root 运行sql文件 右键根目录刷新 刷新后成功 开始 测试 如果需要上传照片,点击创建相册,选择上传文件,选择文件后退出 导入alumbenew2 2.提交表单方式 3.利用ph…...
Xcode手动安装SDK模拟器
1.下载SDK模拟器&Xcode SDK和Xcode官方下载地址 2.下载好后使用命令将SDK导入到Xcode中如下命令 注:我是在/Applications 目录下执行的命令,模拟其地址直接拖拽过来 sudo xcode-select -s Xcode.app xcodebuild -runFirstLaunch xcodebuild -imp…...

Docker安装consul + go使用consul + consul知识
1. 什么是服务注册和发现 假如这个产品已经在线上运行,有一天运营想搞一场促销活动,那么我们相对应的【用户服务】可能就要新开启三个微服务实例来支撑这场促销活动。而与此同时,作为苦逼程序员的你就只有手动去 API gateway 中添加新增的这…...
JWT 漏洞 - 学习手册
0x01:JWT 前导知识 0x0101:JWT 详解 0x02:JWT 漏洞介绍 0x0201:JWT 漏洞介绍 0x03:JWT 挖掘思路 JWT 漏洞挖掘思路 - JWT Payload 敏感信息泄露 备注:通过泄露的 JWT Payload 获取用户的敏感信息&#…...
HTML【知识改变命运】03font 字体标签
题目:在页面上显示"北京"两个字,字体为微软雅黑,颜色为红色,大小为40xp; font标签可以修饰字体的大小,颜色,和字体 属性:color颜色,face字体,size大…...

集师专属知识付费小程序搭建 心理咨询小程序搭建
一、产品简介 集师SaaS知识付费软件,为知识创业者或商家提供一站式内容交付解决方案,助力商家搭建集品牌传播、商业变现和用户运营于一体的线上知识服务系统,覆盖全渠道经营场景,占据每个流量入口,使流量变现快速高效…...

https://www.aitoolpath.com/ 一个工具数据库,目前储存了有2000+各种工具。每日更新
AI 工具爆炸?别怕,这个网站帮你整理好了! 哇塞,兄弟们!AI 时代真的来了!现在各种 AI 工具跟雨后春笋似的,噌噌噌地往外冒。AI 写作、AI 绘画、AI 代码生成……简直是要逆天啊! 可是…...
科技的成就(六十三)
583、八小时工作制 最先提出这种理念的人竟然也是一名企业家,而且还是一名空想社会主义者。这名叫做罗伯特欧文的英国人,也凭借先进的人本管理理念成为了现代人事管理之父。 584、SDN(软件定义网络) "SDN(软件定…...
浅谈抗量子密码学:保护未来的数字安全
一、引言 随着量子计算机技术的发展,传统的加密算法面临前所未有的挑战。量子计算机利用量子位(qubits)的特性,能够在理论上比经典计算机更快地破解现有的加密系统。为了应对这一威胁,研究者们正在开发所谓的“抗量子…...
10款物联网开源嵌入式操作系统对比分析
摘要 本文对目前市场上广受欢迎的10款物联网开源嵌入式操作系统进行了深度对比分析,包括Huawei LiteOS、RT-Thread、AliOS Things等。通过探讨这些操作系统的实时性、可扩展性、特点、运行要求、开发社区活跃度和应用领域等方面,帮助开发者更好地理解它…...

MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...

Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...