嵌入式系统中的并行编程模型:汇总解析与应用

在这里插入图片描述

概述:随着嵌入式系统处理能力的不断提升,并行编程在其中的应用愈发广泛。本文深入探讨了多种专门为嵌入式设计的并行编程模型,包括任务队列模型、消息传递模型、数据并行模型、异构多核并行模型、实时任务调度模型以及函数式并行模型。详细阐述了各模型的原理、优缺点、适用场景,并提供了相应的 C++ 源代码示例及典型开源库介绍,旨在为嵌入式系统开发人员在选择和应用并行编程模型时提供全面的参考与指导,助力其更高效地开发嵌入式并行应用程序。

一、任务队列模型

任务队列模型将需要执行的任务放入一个队列中,由多个线程从队列中获取任务并执行。这种模型可以有效地管理任务的分配和执行顺序,避免了线程之间的直接依赖和复杂的同步问题。

(一)优点

能灵活地分配任务,可根据系统的负载动态调整任务的执行顺序和分配方式。当有新任务加入队列时,空闲线程可以及时获取并执行,提高了系统的资源利用率和响应速度。

(二)缺点

需要额外的内存来存储任务队列和任务的相关信息,增加了一定的内存开销。如果任务之间存在依赖关系,需要在任务的设计和调度中进行额外的处理,以确保任务的正确执行顺序。

(三)适用场景

适用于处理多个相对独立的任务,如传感器数据的采集和处理。不同的传感器数据采集任务可以作为独立的任务放入队列,由多个线程并行处理,提高数据采集和处理的效率。

(四)C++ 源代码示例

#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>std::queue<int> task_queue;
std::mutex mutex_queue;
std::condition_variable cond_var;void fibonacci_task(int n) {if (n == 0) {std::cout << "0 ";return;} else if (n == 1) {std::cout << "1 ";return;} else {int a = 0, b = 1, temp;for (int i = 0; i < n; i++) {std::cout << a << " ";temp = a + b;a = b;b = temp;}std::cout << std::endl;}
}void worker_thread() {while (true) {std::unique_lock<std::mutex> lock(mutex_queue);cond_var.wait(lock, []{ return!task_queue.empty(); });int task = task_queue.front();task_queue.pop();lock.unlock();fibonacci_task(task);}
}int main() {const int num_tasks = 10;std::thread threads[num_tasks];// 创建并启动工作线程for (int i = 0; i < num_tasks; ++i) {threads[i] = std::thread(worker_thread);}// 将任务放入队列for (int i = 0; i < num_tasks; ++i) {std::unique_lock<std::mutex> lock(mutex_queue);task_queue.push(i);lock.unlock();cond_var.notify_one();}// 等待所有线程完成任务for (int i = 0; i < num_tasks; ++i) {threads[i].join();}return 0;
}

(五)典型开源库

cppq 是一个简单、可靠且高效的 C++17 分布式任务队列库,它提供了方便的任务调度和执行功能,适用于各种需要异步处理任务的场景。

二、消息传递模型

消息传递模型中线程之间通过传递消息来进行通信和协调,而不是直接共享内存。每个线程都有自己的消息队列,用于接收和发送消息。

(一)优点

避免了共享内存带来的并发访问问题,如数据竞争和死锁等,提高了系统的稳定性和可靠性。线程之间的通信更加明确和可控,便于进行系统的调试和维护。

(二)缺点

消息传递的开销相对较大,包括消息的复制和传递过程中的系统调用等,可能会影响系统的性能。如果消息的处理不及时,可能会导致消息队列的积压,影响系统的实时性。

(三)适用场景

适用于对可靠性要求较高的嵌入式系统,如航空航天、工业控制等领域。在这些系统中,线程之间的通信需要严格的控制和管理,以确保系统的安全和稳定运行。

(四)C++ 源代码示例

#include <cstdlib>
#include <iostream>
#include <mpl/mpl.hpp>int main() {// 获取通信器 "world" 的引用const mpl::communicator& comm_world{mpl::environment::comm_world()};// 每个进程打印包含处理器名称、在通信器中的 rank 以及通信器大小的消息std::cout << "Hello World! I am running on \"" << mpl::environment::processor_name()<< "\". My rank is " << comm_world.rank() << " out of " << comm_world.size()<< " processes.\n";// 如果有两个或更多进程,则从进程 0 向进程 1 发送消息if (comm_world.size() >= 2) {if (comm_world.rank() == 0) {std::string message{"Hello World!"};comm_world.send(message, 1); // 向 rank 1 发送消息} else if (comm_world.rank() == 1) {std::string message;comm_world.recv(message, 0); // 从 rank 0 接收消息std::cout << "Got: \"" << message << "\"\n";}}return EXIT_SUCCESS;
}

(五)典型开源库

MPL 是一个基于 MPI 标准的 C++17 消息传递库,它为高性能计算提供了现代 C++ 接口,具有类型安全、易于使用等特点,适用于大规模模拟、并行算法研究等高性能计算场景。

三、数据并行模型

数据并行模型将数据分成多个部分,每个部分由一个独立的处理单元进行处理,处理单元之间可以并行执行,从而提高数据处理的效率。

(一)优点

能充分利用硬件的并行处理能力,大大提高数据处理的速度。对于处理大规模数据的嵌入式系统,如视频监控、图像处理等,数据并行模型可以显著提高系统的性能。

(二)缺点

对数据的划分和分配要求较高,如果数据划分不合理,可能会导致处理单元之间的负载不均衡,影响系统的性能。同时,数据并行模型需要处理单元之间进行一定的同步和通信,以确保数据处理的正确性。

(三)适用场景

适用于需要对大量数据进行相同或相似处理的嵌入式系统,如智能交通系统中的车牌识别、医疗设备中的图像诊断等。

(四)C++ 源代码示例

#include <iostream>
#include <CL/sycl.hpp>int main() {const int size = 1024;int a[size], b[size], c[size];// 初始化数组for (int i = 0; i < size; i++) {a[i] = i;b[i] = i * 2;}{// 创建 SYCL 队列cl::sycl::queue q(cl::sycl::default_selector{});// 在设备上分配内存cl::sycl::buffer<int, 1> buffer_a(a, cl::sycl::range<1>(size));cl::sycl::buffer<int, 1> buffer_b(b, cl::sycl::range<1>(size));cl::sycl::buffer<int, 1> buffer_c(c, cl::sycl::range<1>(size));// 提交并行任务q.submit([&](cl::sycl::handler& h) {auto accessor_a = buffer_a.get_access<cl::sycl::access::mode::read>(h);auto accessor_b = buffer_b.get_access<cl::sycl::access::mode::read>(h);auto accessor_c = buffer_c.get_access<cl::sycl::access::mode::write>(h);h.parallel_for<class add_vectors>(cl::sycl::range<1>(size), [=](cl::sycl::id<1> idx) {accessor_c[idx] = accessor_a[idx] + accessor_b[idx];});});// 等待任务完成q.wait();}// 检查结果for (int i = 0; i < size; i++) {if (c[i]!= a[i] + b[i]) {std::cerr << "Error at index " << i << std::endl;return -1;}}std::cout << "Arrays added successfully." << std::endl;return 0;
}

(五)典型开源库

DPC++ 是一个基于 C++ 和 SYCL 的异构并行编程模型,它允许开发者使用标准 C++ 编写跨不同架构的并行代码,包括 CPU、GPU 和 FPGA 等,适用于需要在异构硬件平台上进行高性能计算的场景。

四、异构多核并行模型

异构多核并行模型针对嵌入式异构多核处理器设计,充分发挥不同类型处理器核心的优势,实现并行计算。

(一)优点

能根据不同核心的特点进行任务分配,提高系统的整体性能和能效比。可以在不增加太多硬件成本的情况下,通过合理利用异构多核处理器的资源,满足嵌入式系统对高性能计算的需求。

(二)缺点

需要对异构多核处理器的架构和编程接口有深入的了解,开发难度相对较大。同时,异构多核之间的通信和协同工作也需要进行精心的设计和优化,以确保系统的稳定性和性能。

(三)适用场景

适用于对性能和功耗有严格要求的嵌入式系统,如智能手机、平板电脑、智能机器人等。

(四)C++ 源代码示例

#include <iostream>
#include <CL/cl.hpp>const int matrix_size = 1024;void matrix_multiply(cl::CommandQueue& queue, cl::Buffer& matrix_a, cl::Buffer& matrix_b, cl::Buffer& result_matrix) {// 定义内核函数const char* kernel_source = "__kernel void matrix_multiply_kernel(__global const float* matrix_a, ""__global const float* matrix_b, __global float* result_matrix, ""const int matrix_size) {""int row = get_global_id(0);""int col = get_global_id(1);""float sum = 0.0f;""for (int k = 0; k < matrix_size; k++) {""sum += matrix_a[row * matrix_size + k] * matrix_b[k * matrix_size + col];""}""result_matrix[row * matrix_size + col] = sum;""}";// 创建程序对象cl::Program::Sources sources;sources.push_back({kernel_source, strlen(kernel_source)});cl::Program program(queue.getInfo<CL_QUEUE_CONTEXT>(), sources);// 构建程序program.build("-cl-std=CL1.2");// 创建内核对象cl::Kernel kernel(program, "matrix_multiply_kernel");// 设置内核参数kernel.setArg(0, matrix_a);kernel.setArg(1, matrix_b);kernel.setArg(2, result_matrix);kernel.setArg(3, matrix_size);// 定义全局工作项大小cl::NDRange global_work_size(matrix_size, matrix_size);// 执行内核queue.enqueueNDRangeKernel(kernel, cl::NullRange, global_work_size);
}int main() {// 初始化矩阵数据float matrix_a_data[matrix_size * matrix_size];float matrix_b_data[matrix_size * matrix_size];float result_matrix_data[matrix_size * matrix_size];for (int i = 0; i < matrix_size * matrix_size; i++) {matrix_a_data[i] = static_cast<float>(rand()) / RAND_MAX;matrix_b_data[i] = static_cast<float>(rand()) / RAND_MAX;}try {// 获取平台和设备信息std::vector<cl::Platform> platforms;cl::Platform::get(&platforms);cl::Device device;for (const auto& platform : platforms) {std::vector<cl::Device> devices;platform.getDevices(CL_DEVICE_TYPE_ALL, &devices);for (const auto& dev : devices) {if (dev.getInfo<CL_DEVICE_TYPE>() == CL_DEVICE_TYPE_GPU ||dev.getInfo<CL_DEVICE_TYPE>() == CL_DEVICE_TYPE_CPU) {device = dev;break;}}if (device()!= 0) {break;}}// 创建上下文和命令队列cl::Context context({device});cl::CommandQueue queue(context, device);// 在设备上创建缓冲区cl::Buffer matrix_a(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float) * matrix_size * matrix_size, matrix_a_data);cl::Buffer matrix_b(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float) * matrix_size * matrix_size, matrix_b_data);cl::Buffer result_matrix(context, CL_MEM_WRITE_ONLY, sizeof(float) * matrix_size * matrix_size);// 执行矩阵乘法matrix_multiply(queue, matrix_a, matrix_b, result_matrix);// 将结果从设备读回主机queue.enqueueReadBuffer(result_matrix, CL_TRUE, 0, sizeof(float) * matrix_size * matrix_size, result_matrix_data);// 输出结果的一部分for (int i = 0; i < 10; i++) {for (int j = 0; j < 10; j++) {std::cout << result_matrix_data[i * matrix_size + j] << " ";}std::cout << std::endl;}} catch (cl::Error& e) {std::cerr << "OpenCL error: " << e.what() << " (" << e.err() << ")" << std::endl;return -1;}return 0;
}

(五)典型开源库

OpenCL 是一个用于异构并行计算的开放标准和编程框架,它允许开发者使用 C、C++ 等语言在不同的异构硬件平台上进行并行编程,包括 CPU、GPU、FPGA 等,适用于各种需要利用异构硬件加速计算的场景。

五、实时任务调度模型

实时任务调度模型根据任务的实时性要求和优先级,对任务进行调度和分配,确保关键任务能够在规定的时间内完成。

(一)优点

能保证系统的实时性,对于一些对时间敏感的嵌入式应用,如汽车电子控制系统、工业自动化控制系统等,实时任务调度模型可以确保系统的可靠运行。

(二)缺点

需要对任务的实时性要求和优先级进行准确的评估和设置,否则可能会导致系统的性能下降或任务错过截止时间。同时,抢占式调度可能会引入一定的上下文切换开销,影响系统的效率。

(三)适用场景

适用于对实时性要求极高的嵌入式系统,特别是那些涉及到人身安全和关键任务控制的领域。

(四)C++ 源代码示例

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <ctime>
#include <ratio>
#include <chrono>std::mutex mutex_task;
std::condition_variable cond_var;
bool high_priority_task_ready = false;
bool medium_priority_task_ready = false;
bool low_priority_task_ready = false;void high_priority_task() {std::this_thread::sleep_for(std::chrono::seconds(1));{std::lock_guard<std::mutex> lock(mutex_task);high_priority_task_ready = true;}cond_var.notify_one();
}void medium_priority_task() {std::this_thread::sleep_for(std::chrono::seconds(2));{std::lock_guard<std::mutex> lock(mutex_task);medium_priority_task_ready = true;}cond_var.notify_one();
}void low_priority_task() {std::this_thread::sleep_for(std::chrono::seconds(3));{std::lock_guard<std::mutex> lock(mutex_task);low_priority_task_ready = true;}cond_var.notify_one();
}void scheduler() {std::unique_lock<std::mutex> lock(mutex_task);while (!high_priority_task_ready ||!medium_priority_task_ready ||!low_priority_task_ready) {if (high_priority_task_ready) {std::cout << "Executing high priority task." << std::endl;high_priority_task_ready = false;} else if (medium_priority_task_ready) {std::cout << "Executing medium priority task." << std::endl;medium_priority_task_ready = false;} else if (low_priority_task_ready) {std::cout << "Executing low priority task." << std::endl;low_priority_task_ready = false;} else {cond_var.wait(lock);}}lock.unlock();
}int main() {std::thread high_thread(high_priority_task);std::thread medium_thread(medium_priority_task);std::thread low_thread(low_priority_task);std::thread scheduler_thread(scheduler);high_thread.join();medium_thread.join();low_thread.join();scheduler_thread.join();return 0;
}

(五)典型开源库

RTEMS 是一个开源的实时操作系统,它提供了丰富的实时任务调度和管理功能,包括优先级调度、时间片轮转调度等,适用于各种对实时性要求较高的嵌入式系统,如航空航天、工业控制等领域 。

六、函数式并行模型

函数式并行模型将程序表示为一系列不可变数据和纯函数的组合,函数之间可以并行执行,因为它们没有副作用,不会相互干扰。

(一)优点

代码简洁、易于理解和维护,函数的并行执行可以提高系统的性能,特别是对于处理大规模数据和复杂计算的嵌入式系统。由于函数没有副作用,不会出现数据竞争和并发访问问题,提高了系统的可靠性。

(二)缺点

对开发人员的编程习惯和思维方式有一定的要求,需要熟悉函数式编程的概念和技巧。同时,函数式并行模型可能会引入一定的额外开销,如函数的调用和数据的复制等,影响系统的性能。

(三)适用场景

适用于对代码简洁性和可靠性要求较高的嵌入式系统,如金融交易系统、智能家居控制系统等。

(四)C++ 源代码示例

#include <iostream>
#include <future>int fibonacci(int n) {if (n == 0) {return 0;} else if (n == 1) {return 1;} else {return fibonacci(n - 1) + fibonacci(n - 2);}
}int main() {int n = 10;std::future<int> future_result = std::async(std::launch::async, fibonacci, n);std::cout << "Calculating Fibonacci(" << n << ")..." << std::endl;int result = future_result.get();std::cout << "Fibonacci(" << n << ") = " << result << std::endl;return 0;
}

(五)典型开源库

range-v3 是一个 C++ 函数式编程库,它提供了类似于 Haskell 中的 range 概念和相关的函数式操作,如 map、filter、reduce 等,可以方便地进行数据处理和并行计算,适用于需要简洁、高效地处理数据的场景 。

综上所述,不同的并行编程模型在嵌入式系统中各有优劣和适用场景。开发人员在选择时,需要综合考虑系统的资源限制、实时性要求、任务特性以及自身的开发能力等因素,以确定最适合的并行编程模型,从而实现高效、可靠的嵌入式系统开发。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/488539.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MTK 配置文件梳理

文章目录 MTK 日常配置总结屏幕默认横竖屏显示ro.build.characteristics 属性修改修改点一&#xff1a;build\core\product_config.mk修改点二&#xff1a;build\make\core\main.mk修改是否成功&#xff0c;adb 验证 配置部分系统app handheld_product.mk配置系统属性、第三方应…

CentOS 上如何查看 SSH 服务使用的端口号?

我们知道&#xff0c;linux操作系统中的SSH默认情况下&#xff0c;端口是使用22&#xff0c;但是有些线上服务器并不是使用的默认端口&#xff0c;那么这个时候&#xff0c;我们应该如何快速知道SSH使用的哪个端口呢&#xff1f; 1、通过配置文件查看 cat /etc/ssh/sshd_confi…

关于Redis哨兵机制实验操作步骤

需要搭建帮助的可以去taobao搜索Easy Company技术服务&#xff0c;谢谢&#xff01;&#xff01;&#xff01; 需要搭建帮助的可以去taobao搜索Easy Company技术服务&#xff0c;谢谢&#xff01;&#xff01;&#xff01; 一、配置哨兵(sentinel) 创建三个哨兵配置文件&…

Vue 集成地图

电子地图应用广泛&#xff1a; 网约车 : 在网约车 场景中实现 准定位 、导航 、司乘同显 &#xff0c;精准计费 智慧物流、生活服务等&#xff0c;本专题课程囊括各类应用场景 学习 电子地图解决方案&#xff0c;满足学员工作学习各类需求。 基础知识 学习 集成 地图之前需…

Qt-chart 画折线图(文字x轴)

图 代码 QLineSeries *seriesReality new QLineSeries();seriesReality->setColor(Qt::green);QLineSeries *seriesTar new QLineSeries();seriesTar->setColor(Qt::yellow);// 创建并配置X轴&#xff08;文字标签&#xff09;QStringList categories;for (int i 0; …

农业园区气象站

农业园区气象站是一种专为农业生产和科研设计的气象监测设备&#xff0c;它集成了多种传感器和技术&#xff0c;用于实时、准确地监测和记录农业园区内的气象数据。以下是农业园区气象站的主要功能和用处&#xff1a; 一、主要功能 实时监测&#xff1a;农业园区气象站能够实时…

DocFlow票据AI自动化处理工具:出色的文档解析+抽取能力,提升企业文档数字化管理效能

目录 财务应付 金融信贷业务 近期&#xff0c;DocFlow票据自动化产品正式上线。DocFlow是一款票据AI自动化处理工具&#xff0c;支持不同版式单据智能分类扩展&#xff0c;可选功能插件配置流程&#xff0c;满足多样业务场景。 随着全球化与信息化进程&#xff0c;企业的文件…

用于卫星影像间接RPC模型精化的通用光束法平差方法

引言 介绍了通用RPC模型的表达式&#xff0c;which has been down to death 描述了RPC模型产生误差的原因——主要与定义传感器方位的姿态角有关。 每个影像都会对应一个三维点云&#xff0c;但是对同一地物拍摄的不同影像对应出来的三维点云是不一样的&#xff0c;所以才需…

Cerebras 推出 CePO,填补推理与规划能力的关键空白

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

AAAI 2025 camera ready提交注意事项

您必须在截止日期前填写、签署并返回 AAAI 版权表&#xff08;除非 AAAI Press 指示使用 AAAI 分发许可证&#xff09;。 您必须根据作者的格式说明阅读并格式化您的论文和 PDF。 您必须使用我们的电子提交表格准时提交您的电子文件和摘要。 您必须向 AAAI Press 支付任何所需的…

Maven学习(Maven项目模块化。模块间“继承“机制。父(工程),子项目(模块)间聚合)

目录 一、Maven项目模块化&#xff1f; &#xff08;1&#xff09;基本介绍。 &#xff08;2&#xff09;汽车模块化生产再聚合组装。 &#xff08;3&#xff09;Maven项目模块化图解。 1、maven_parent。 2、maven_pojo。 3、maven_dao。 4、maven_service。 5、maven_web。 6…

关于GaussDB

一、GaussDB的层级关系 &#xff0c;关于schemas的定位&#xff0c;到底是个什么&#xff0c;其实就可以理解为一个文件夹 数据库服务器 --> databases --> schemas --> tables schema类似于文件夹&#xff0c;一个数据库database里面可以有多个文件夹&#xff0c;每…

对流层路径延迟对SAR方位压缩的影响(CSDN_20240301)

目录 仿真参数 方位向脉冲压缩与高阶多普勒参数的关系 仿真结果 2m分辨率 1m分辨率 0.5m分辨率 0.3m分辨率 0.2m分辨率 0.1m分辨率 0.05m分辨率 小结 对流层路径延迟对方位脉冲压缩的影响 仿真参数 地球参数 赤道半径&#xff08;m&#xff09; 6378140 极半径&a…

xss原理分析与剖析

001 第三方劫持 (外调J/C)&#xff1a; 本方法是我看长短短贴代码时知晓的&#xff0c;这篇文章我只是把这个攻击手法整理了出来&#xff0c;来说明这个漏洞&#xff0c;这个攻击手法并不是我发现的&#xff0c;我也不是太清楚是谁。“第三方劫持”就是把资源域的服务器的权限…

使用阿里云搭建镜像仓库

流程如图 接着登录到安装docker的客户机上 #执行如下操作 先登录 docker login --usernamealiyun2933717661 crpi-q5qqr0d39o6em66u.cn-beijing.personal.cr.aliyuncs.com Password: #输入密码 WARNING! Your password will be stored unencrypted in /root/.docker/config.j…

中国卫生健康统计年鉴Excel+PDF电子版2022年-社科数据

中国卫生健康统计年鉴ExcelPDF电子版2022年-社科数据https://download.csdn.net/download/paofuluolijiang/90028752 《中国卫生健康统计年鉴》2022年版涵盖了2006至2022年间的卫生健康相关数据&#xff0c;提供了丰富的统计信息。该年鉴包含16个部分&#xff0c;内容涉及医疗…

HBuilderX(uni-app)Vue3路由传参和接收路由参数!!

uni-app搭建小程序时候Vue3语法接收路由参数&#xff0c;去官方文档查看&#xff0c;是onLoad的option接收参数&#xff0c;我试过&#xff0c;接收不到&#xff0c;上网查各种方法也是不太行&#xff0c;最后自己琢磨出来了&#xff0c;这参数藏得还挺深&#xff01;&#xff…

手机租赁系统开发全流程解析与实用指南

内容概要 在如今快速发展的科技时代&#xff0c;手机租赁系统已经成为一种新兴的商业模式&#xff0c;非常符合当下市场需求。那么&#xff0c;在开发这样一个系统的时候&#xff0c;首先要从需求分析和市场调研开始。在这一阶段&#xff0c;你需要了解用户需要什么&#xff0…

【Compose multiplatform教程】01 创建你的多平台项目 <官网搬运>

这是 “创建带有共享逻辑和用户界面的 Compose 多平台应用” 教程的第一部分。 第一步&#xff1a;创建你的多平台项目 第二步&#xff1a;探究可组合代码 第三步&#xff1a;修改项目 第四步&#xff1a;创建你自己的应用程序 在这里&#xff0c;你将学习如何使用 Kotlin 多平…

vue2:el-select中的@change事件如何传入自定义参数

在 Element UI 中,el-select 组件用于创建一个下拉选择框。当选项发生变化时,你可以使用 @change 事件来监听这个变化。默认传入的是选中项的值(如果是多选,则传入一个数组) 但是有些时候需要传入额外的自定义参数,可以通过如下方式实现 1、template中定义事件响应函数时…