奉贤专业网站建设/网站关键词排名优化
生产者-消费者模式是多线程并发编程中一个非常经典的模式,它通过解耦生产者和消费者的关系,使得两者可以独立工作,从而提高系统的并发性和可扩展性。本文将详细介绍生产者-消费者模式的概念、实现方式以及应用场景。
1 生产者-消费者模式概述
生产者-消费者模式包含两类线程:
- 生产者线程:负责生产数据,并将数据放入共享数据区。
- 消费者线程:负责从共享数据区中取出数据并进行消费。
为了解耦生产者和消费者的关系,通常会使用一个共享的数据区域(如队列)作为缓冲区。生产者将数据放入缓冲区,消费者从缓冲区中取出数据,两者之间不需要直接通信。
共享数据区域需要具备以下功能:
- 当缓冲区已满时,阻塞生产者线程,防止其继续生产数据。
- 当缓冲区为空时,阻塞消费者线程,防止其继续消费数据。
2 wait/notify
的消息通知机制
wait/notify
是 Java 中用于线程间通信的经典机制,通过 Object
类提供的 wait
和 notify/notifyAll
方法,可以实现线程的等待和唤醒。下面将详细介绍 wait/notify
机制的使用方法、注意事项以及常见问题的解决方案。
2.1 wait
方法
wait
方法用于将当前线程置入休眠状态,直到其他线程调用 notify
或 notifyAll
方法唤醒它。
- 调用条件:
wait
方法必须在同步方法或同步块中调用,即线程必须持有对象的监视器锁(monitor lock)。 - 释放锁:调用
wait
方法后,当前线程会释放锁,并进入等待状态。 - 异常:如果线程在调用
wait
方法时没有持有锁,则会抛出IllegalMonitorStateException
异常。
示例代码:
synchronized (lockObject) {try {lockObject.wait();} catch (InterruptedException e) {e.printStackTrace();}
}
2.2 notify
方法
notify
方法用于唤醒一个正在等待该对象监视器锁的线程。
- 调用条件:
notify
方法也必须在同步方法或同步块中调用,线程必须持有对象的监视器锁。 - 唤醒线程:
notify
方法会从等待队列中随机选择一个线程进行唤醒,使其从wait
方法处退出,并进入同步队列,等待获取锁。 - 释放锁:调用
notify
后,当前线程不会立即释放锁,而是在退出同步块后才会释放锁。
示例代码:
synchronized (lockObject) {lockObject.notify();
}
2.3 notifyAll
方法
notifyAll
方法与 notify
方法类似,但它会唤醒所有正在等待该对象监视器锁的线程。
- 唤醒所有线程:
notifyAll
方法会将所有等待队列中的线程移入同步队列,等待获取锁。
示例代码:
synchronized (lockObject) {lockObject.notifyAll();
}
2.4 wait/notify
机制的常见问题及解决方案
2.4.1 notify
早期通知
问题描述:如果 notify
方法在 wait
方法之前被调用,可能会导致通知遗漏,使得等待的线程一直处于阻塞状态。
示例代码:
public class EarlyNotify {private static String lockObject = "";public static void main(String[] args) {WaitThread waitThread = new WaitThread(lockObject);NotifyThread notifyThread = new NotifyThread(lockObject);notifyThread.start();try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}waitThread.start();}static class WaitThread extends Thread {private String lock;public WaitThread(String lock) {this.lock = lock;}@Overridepublic void run() {synchronized (lock) {try {System.out.println(Thread.currentThread().getName() + " 进去代码块");System.out.println(Thread.currentThread().getName() + " 开始wait");lock.wait();System.out.println(Thread.currentThread().getName() + " 结束wait");} catch (InterruptedException e) {e.printStackTrace();}}}}static class NotifyThread extends Thread {private String lock;public NotifyThread(String lock) {this.lock = lock;}@Overridepublic void run() {synchronized (lock) {System.out.println(Thread.currentThread().getName() + " 进去代码块");System.out.println(Thread.currentThread().getName() + " 开始notify");lock.notify();System.out.println(Thread.currentThread().getName() + " 结束开始notify");}}}
}
解决方案:添加一个状态标志,在 wait
方法调用前判断状态是否已经改变。
优化后的代码:
public class EarlyNotify {private static String lockObject = "";private static boolean isWait = true;public static void main(String[] args) {WaitThread waitThread = new WaitThread(lockObject);NotifyThread notifyThread = new NotifyThread(lockObject);notifyThread.start();try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}waitThread.start();}static class WaitThread extends Thread {private String lock;public WaitThread(String lock) {this.lock = lock;}@Overridepublic void run() {synchronized (lock) {try {while (isWait) {System.out.println(Thread.currentThread().getName() + " 进去代码块");System.out.println(Thread.currentThread().getName() + " 开始wait");lock.wait();System.out.println(Thread.currentThread().getName() + " 结束wait");}} catch (InterruptedException e) {e.printStackTrace();}}}}static class NotifyThread extends Thread {private String lock;public NotifyThread(String lock) {this.lock = lock;}@Overridepublic void run() {synchronized (lock) {System.out.println(Thread.currentThread().getName() + " 进去代码块");System.out.println(Thread.currentThread().getName() + " 开始notify");lock.notifyAll();isWait = false;System.out.println(Thread.currentThread().getName() + " 结束开始notify");}}}
}
2.4.2 等待条件发生变化
问题描述:如果线程在等待时接收到通知,但之后等待的条件发生了变化,可能会导致程序出错。
示例代码:
public class ConditionChange {private static List<String> lockObject = new ArrayList();public static void main(String[] args) {Consumer consumer1 = new Consumer(lockObject);Consumer consumer2 = new Consumer(lockObject);Productor productor = new Productor(lockObject);consumer1.start();consumer2.start();productor.start();}static class Consumer extends Thread {private List<String> lock;public Consumer(List lock) {this.lock = lock;}@Overridepublic void run() {synchronized (lock) {try {if (lock.isEmpty()) {System.out.println(Thread.currentThread().getName() + " list为空");System.out.println(Thread.currentThread().getName() + " 调用wait方法");lock.wait();System.out.println(Thread.currentThread().getName() + " wait方法结束");}String element = lock.remove(0);System.out.println(Thread.currentThread().getName() + " 取出第一个元素为:" + element);} catch (InterruptedException e) {e.printStackTrace();}}}}static class Productor extends Thread {private List<String> lock;public Productor(List lock) {this.lock = lock;}@Overridepublic void run() {synchronized (lock) {System.out.println(Thread.currentThread().getName() + " 开始添加元素");lock.add(Thread.currentThread().getName());lock.notifyAll();}}}
}
解决方案:在 wait
方法退出后再次检查等待条件。
优化后的代码:
public class ConditionChange {private static List<String> lockObject = new ArrayList();public static void main(String[] args) {Consumer consumer1 = new Consumer(lockObject);Consumer consumer2 = new Consumer(lockObject);Productor productor = new Productor(lockObject);consumer1.start();consumer2.start();productor.start();}static class Consumer extends Thread {private List<String> lock;public Consumer(List lock) {this.lock = lock;}@Overridepublic void run() {synchronized (lock) {try {while (lock.isEmpty()) {System.out.println(Thread.currentThread().getName() + " list为空");System.out.println(Thread.currentThread().getName() + " 调用wait方法");lock.wait();System.out.println(Thread.currentThread().getName() + " wait方法结束");}String element = lock.remove(0);System.out.println(Thread.currentThread().getName() + " 取出第一个元素为:" + element);} catch (InterruptedException e) {e.printStackTrace();}}}}static class Productor extends Thread {private List<String> lock;public Productor(List lock) {this.lock = lock;}@Overridepublic void run() {synchronized (lock) {System.out.println(Thread.currentThread().getName() + " 开始添加元素");lock.add(Thread.currentThread().getName());lock.notifyAll();}}}
}
2.4.3 “假死”状态
问题描述:在多消费者和多生产者的情况下,使用 notify
方法可能会导致“假死”状态,即所有线程都处于等待状态,无法被唤醒。
原因分析:如果多个生产者线程调用了 wait
方法阻塞等待,其中一个生产者线程获取到对象锁后使用 notify
方法唤醒其他线程,如果唤醒的仍然是生产者线程,就会导致所有生产者线程都处于等待状态。
解决方案:将 notify
方法替换成 notifyAll
方法。
总结:在使用 wait/notify
机制时,应遵循以下原则:
- 永远在
while
循环中对条件进行判断,而不是在if
语句中进行wait
条件的判断。 - 使用
notifyAll
而不是notify
。
基本的使用范式如下:
synchronized (sharedObject) {while (condition) {sharedObject.wait();// (Releases lock, and reacquires on wakeup)}// do action based upon condition e.g. take or put into queue
}
3 实现生产者-消费者模式的三种方式
生产者-消费者模式可以通过以下三种方式实现:
3.1 使用 Object
的 wait/notify
机制
Object
类提供了 wait
和 notify/notifyAll
方法,用于线程间的通信。
wait
方法:使当前线程进入等待状态,直到其他线程调用notify
或notifyAll
方法唤醒它。调用wait
方法前,线程必须持有对象的监视器锁,否则会抛出IllegalMonitorStateException
异常。notify
方法:唤醒一个正在等待该对象监视器锁的线程。如果有多个线程在等待,则随机选择一个唤醒。notifyAll
方法:唤醒所有正在等待该对象监视器锁的线程。
示例代码:
public class ProductorConsumer {private static LinkedList<Integer> list = new LinkedList<>();private static final int MAX_SIZE = 10;public static void main(String[] args) {ExecutorService service = Executors.newFixedThreadPool(15);for (int i = 0; i < 5; i++) {service.submit(new Productor());}for (int i = 0; i < 10; i++) {service.submit(new Consumer());}}static class Productor implements Runnable {@Overridepublic void run() {while (true) {synchronized (list) {try {while (list.size() == MAX_SIZE) {System.out.println("生产者" + Thread.currentThread().getName() + " list以达到最大容量,进行wait");list.wait();}int data = new Random().nextInt();System.out.println("生产者" + Thread.currentThread().getName() + " 生产数据" + data);list.add(data);list.notifyAll();} catch (InterruptedException e) {e.printStackTrace();}}}}}static class Consumer implements Runnable {@Overridepublic void run() {while (true) {synchronized (list) {try {while (list.isEmpty()) {System.out.println("消费者" + Thread.currentThread().getName() + " list为空,进行wait");list.wait();}int data = list.removeFirst();System.out.println("消费者" + Thread.currentThread().getName() + " 消费数据:" + data);list.notifyAll();} catch (InterruptedException e) {e.printStackTrace();}}}}}
}
3.2 使用 Lock
和 Condition
的 await/signal
机制
Lock
和 Condition
提供了比 Object
的 wait/notify
更灵活的线程通信机制。Condition
对象可以通过 lock.newCondition()
创建,并提供了 await
和 signal/signalAll
方法。
await
方法:使当前线程进入等待状态,直到其他线程调用signal
或signalAll
方法唤醒它。signal
方法:唤醒一个正在等待该Condition
的线程。signalAll
方法:唤醒所有正在等待该Condition
的线程。
示例代码:
public class ProductorConsumer {private static LinkedList<Integer> list = new LinkedList<>();private static final int MAX_SIZE = 10;private static ReentrantLock lock = new ReentrantLock();private static Condition full = lock.newCondition();private static Condition empty = lock.newCondition();public static void main(String[] args) {ExecutorService service = Executors.newFixedThreadPool(15);for (int i = 0; i < 5; i++) {service.submit(new Productor());}for (int i = 0; i < 10; i++) {service.submit(new Consumer());}}static class Productor implements Runnable {@Overridepublic void run() {while (true) {lock.lock();try {while (list.size() == MAX_SIZE) {System.out.println("生产者" + Thread.currentThread().getName() + " list以达到最大容量,进行wait");full.await();}int data = new Random().nextInt();System.out.println("生产者" + Thread.currentThread().getName() + " 生产数据" + data);list.add(data);empty.signalAll();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}}}static class Consumer implements Runnable {@Overridepublic void run() {while (true) {lock.lock();try {while (list.isEmpty()) {System.out.println("消费者" + Thread.currentThread().getName() + " list为空,进行wait");empty.await();}int data = list.removeFirst();System.out.println("消费者" + Thread.currentThread().getName() + " 消费数据:" + data);full.signalAll();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}}}
}
3.3 使用 BlockingQueue
实现
BlockingQueue
是 Java 并发包中提供的一个接口,它提供了可阻塞的插入和移除操作。BlockingQueue
非常适合用来实现生产者-消费者模型,因为它可以自动处理线程的阻塞和唤醒。
put
方法:如果队列已满,则阻塞生产者线程,直到队列有空闲空间。take
方法:如果队列为空,则阻塞消费者线程,直到队列中有数据。
示例代码:
public class ProductorConsumer {private static LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);public static void main(String[] args) {ExecutorService service = Executors.newFixedThreadPool(15);for (int i = 0; i < 5; i++) {service.submit(new Productor());}for (int i = 0; i < 10; i++) {service.submit(new Consumer());}}static class Productor implements Runnable {@Overridepublic void run() {try {while (true) {int data = new Random().nextInt();System.out.println("生产者" + Thread.currentThread().getName() + " 生产数据" + data);queue.put(data);}} catch (InterruptedException e) {e.printStackTrace();}}}static class Consumer implements Runnable {@Overridepublic void run() {try {while (true) {int data = queue.take();System.out.println("消费者" + Thread.currentThread().getName() + " 消费数据:" + data);}} catch (InterruptedException e) {e.printStackTrace();}}}
}
4 生产者-消费者模式的应用场景
生产者-消费者模式广泛应用于以下场景:
- 任务执行框架:如
Executor
框架,将任务的提交和执行解耦,提交任务的操作相当于生产者,执行任务的操作相当于消费者。 - 消息中间件:如 MQ(消息队列),用户下单相当于生产者,处理订单的线程相当于消费者。
- 任务处理时间较长:如上传附件并处理,用户上传附件相当于生产者,处理附件的线程相当于消费者。
5 生产者-消费者模式的优点
- 解耦:生产者和消费者之间通过缓冲区进行通信,彼此独立,简化了系统的复杂性。
- 复用:生产者和消费者可以独立复用和扩展,提高了代码的可维护性。
- 调整并发数:可以根据生产者和消费者的处理速度调整并发数,优化系统性能。
- 异步:生产者和消费者各司其职,生产者不需要等待消费者处理完数据,消费者也不需要等待生产者生产数据,提高了系统的响应速度。
- 支持分布式:生产者和消费者可以通过分布式队列进行通信,支持分布式系统的扩展。
6 小结
生产者-消费者模式是多线程并发编程中的经典模式,通过解耦生产者和消费者的关系,提高了系统的并发性和可扩展性。本文介绍了三种实现生产者-消费者模式的方式,并给出了相应的示例代码。通过理解这些实现方式,可以更好地应用生产者-消费者模式解决实际问题。
7 思维导图
8 参考链接
从根上理解生产者-消费者模式
相关文章:

生产者-消费者模式:多线程并发协作的经典案例
生产者-消费者模式是多线程并发编程中一个非常经典的模式,它通过解耦生产者和消费者的关系,使得两者可以独立工作,从而提高系统的并发性和可扩展性。本文将详细介绍生产者-消费者模式的概念、实现方式以及应用场景。 1 生产者-消费者模式概述…...

数据库-mysql(基本语句)
演示工具:navicat 连接:mydb 一.操作数据库 1.创建数据库 ①create database 数据库名称 //普通创建 ②create database if not exists 数据库名称 //创建数据库,判断不存在,再创建: 使用指定数据库 use 数据库…...

android12L super.img 解压缩及其挂载到ubuntu18.04
本文介绍如何在Ubuntu18.04上解压缩高通平台Android12L的super.img,并将其挂载到系统中查看内容。 在源码的根目录下,执行如下命令: out/host/linux-x86/bin/simg2img out/target/product/msmnile_gvmq/super.img super.img_rawmkdir super…...

flask简易版的后端服务创建接口(python)
1.pip install安装Flask和CORS 2.创建http_server.py文件,内容如下 """ ============================ 简易版的后端服务 ============================ """ from flask import Flask, request, jsonify from flask_cors import CORS app = F…...

小程序入门学习(四)之全局配置
一、 全局配置文件及常用的配置项 小程序根目录下的 app.json 文件是小程序的全局配置文件。常用的配置项如下: pages:记录当前小程序所有页面的存放路径 window:全局设置小程序窗口的外观 tabBar:设置小程序底部的 tabBar 效…...

PHP使用RabbitMQ(正常连接与开启SSL验证后的连接)
代码中包含了PHP在一般情况下使用方法和RabbitMQ开启了SSL验证后的使用方法(我这边消费队列是使用接口请求的方式,每次只从中取出一条) 安装amqp扩展 PHP使用RabbitMQ前,需要安装amqp扩展,之前文章中介绍了Windows环…...

轻量级视觉骨干网络 MobileMamba: Lightweight Multi-Receptive Visual Mamba Network
MobileMamba 快速链接解决问题:视觉模型在移动设备端性能和效果的平衡性解决方法:改进网络结构训练和测试策略网络结构改进训练和测试策略 实验支撑:图像分类、分割,目标检测等图像分类结果对比目标检测和实例分割结果对比语义分割…...

科技云报到:数智化转型风高浪急,天翼云如何助力产业踏浪而行?
科技云报到原创。 捷径消亡,破旧立新,是今年千行百业的共同底色。 穿越产业周期,用数字化的力量重塑企业经营与增长的逻辑,再次成为数字化技术应用的主旋律,也是下一阶段产业投资的重点。 随着数字化转型行至“深水区…...

dockerfile部署前后端(vue+springboot)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言0.环境说明和准备1.前端多环境打包1.1前端多环境设置1.2打包 2.后端项目多环境配置以及打包2.1后端多环境配置2.2项目打包 3.文件上传4.后端镜像制作4.1dockerf…...

c语言的思维导图
之前已经全部学完c语言了,所以为了更好的复习回顾,我做了一份c语言超详细的思维导图,帮助实现一张图就可以复习,避免盲目, 由于平台不支持直接发上图,有想要的小伙伴,可以私信找我要原件...

Android 拍照(有无存储权限两种方案,兼容Q及以上版本)
在某些行业,APP可能被禁止使用存储权限,或公司在写SDK功能,不方便获取权限 所以需要有 无存储权限拍照方案。这里两种方案都列出里。 对于写入权限,在高版本中,已经废弃, 不可用文件写入读取权限…...

MongoDB在自动化设备上的应用示例
发现MongoDB特别适合自动化检测数据的存储。。。 例如一个晶圆检测项目,定义其数据结构如下 #pragma once #include <vector> #include <QString> #include <QRectF> #include <string> #include <memory>class tpoWafer; class tp…...

draggable插件——实现元素的拖动排序——拖动和不可拖动的两种情况处理
最近在写后台管理系统的时候,遇到一个需求,就是关于拖动排序的功能。 我之前是写过一个关于拖动表格的功能,此功能可以实现表格中的每一行数据上下拖动实现排序的效果。 vue——实现表格的拖拽排序功能——技能提升 但是目前我这边的需求是…...

Redux的使用
到如今redux的已经不是react程序中必须的一部分内容了, 我们应该在本地需要大量更新全局变量时才使用它! redux vs reducer reducer的工作机制: 手动构造action对象传入dispatch函数中 dispatch函数将 action传入reducer当中 reducer结合当前state与a…...

【JAVA】Java高级:多数据源管理与Sharding:数据分片(Sharding)技术的实现与实践
大规模分布式系统,数据存储和管理变得越来越复杂。随着用户数量和数据量的急剧增加,单一数据库往往难以承载如此庞大的负载。这时,数据分片(Sharding)技术应运而生。数据分片是一种将数据水平切分到多个数据库实例的技…...

ASP.NET Core 9.0 静态资产传递优化 (MapStaticAssets )
一、结论 💢先看结论吧, MapStaticAssets 在大多数情况下可以替换 UseStaticFiles,它已针对为应用在生成和发布时了解的资产提供服务进行了优化。 如果应用服务来自其他位置(如磁盘或嵌入资源)的资产,则应…...

LeetCode刷题day18——贪心
LeetCode刷题day18——贪心 135. 分发糖果分析: 406. 根据身高重建队列分析:for (auto& p : people) 昨天写了一道,今天写了一道,都有思路,却不能全整对。昨天和小伙伴聊天,说是因为最近作业多…...

MATLAB Simulink® - 智能分拣系统
系列文章目录 前言 本示例展示了如何在虚幻引擎 环境中对四种不同形状的标准 PVC 管件实施半结构化智能分拣。本示例使用 Universal Robots UR5e cobot 执行垃圾箱拣选任务,从而成功检测并分类物体。cobot 的末端执行器是一个吸力抓手,它使 cobot 能够拾…...

linuxCNC(五)HAL驱动的指令介绍
HAL驱动的构成 指令举例详解 从终端进入到HAL命令行,执行halrun,即可进入halcmd命令行 # halrun指令描述oadrt加载comoonent,loadrt threads name1 period1创建新线程loadusr halmeter加载万用表UI界面loadusr halscope加载示波器UI界面sho…...

STM32 进阶 定时器3 通用定时器 案例2:测量PWM的频率/周期
需求分析 上一个案例我们输出了PWM波,这个案例我们使用输入捕获功能,来测试PWM波的频率/周期。 把测到的结果通过串口发送到电脑,检查测试的结果。 如何测量 1、输入捕获功能主要是:测量输入通道的上升沿和下降沿 2、让第一个…...

第一节、电路连接【51单片机-TB6600驱动器-步进电机教程】
摘要:本节介绍如何搭建一个51单片机TB6600驱动器步进电机控制电路,所用材料均为常见的模块,简单高效的方式搭建起硬件环境 一、硬件清单 ①51单片机最小控制系统 ②USB转TTL模块 ③开关电源 ④TB6600步进电机驱动器 ⑤二相四线步进电机 ⑥电…...

【通俗理解】Koopman算符与非线性动力系统分析
【通俗理解】Koopman算符与非线性动力系统分析 关键词: #Koopman算符 Koopman Operator #非线性动力系统 Nonlinear Dynamical System #无穷维线性算子 Infinite-Dimensional Linear Operator #演化分析 Evolution Analysis #Bernard Koopman Bernard Koopman 第…...

mybatis plus打印sql日志
1、官方文档 使用配置 | MyBatis-Plus 2、日志实现 MyBatis-Plus 提供了多种日志实现(log-impl),用于记录 SQL 语句和相关操作,帮助开发者进行调试和监控数据库操作。以下是一些可用的日志实现及其说明: StdOutImpl…...

ObjectMapper
ObjectMapper 是 Jackson 库中非常重要的一个类,它是 JSON 和 Java 对象之间进行序列化与反序列化的核心工具。ObjectMapper 的底层实现是基于 Jackson 的数据绑定模型,它将 Java 对象与 JSON 数据转换为互通格式。 1. ObjectMapper 的设计与核心功能 O…...

新增白名单赋予应用安装权限
目录 相关问题 具体实现 相关问题 安装app到/data/分区时,如何在安装阶段就赋予权限,无需请求权限 具体实现 frameworks/base/core/res/res/values/config.xml <!-- For whitelis apk --><string-array translatable"false" nam…...

传奇996_51——脱下装备,附加属性设为0
奶奶的lua怎么都修改不了,可以调用txt的 ; LINKPICKUPITEM ; ChangeitemaddvaLue -1 5 0 ; GETITEMADDVALUE 3 5 M10 ; SENDUPGRADEITEM ; SENDMSG 9 你的衣服附加了<$STR(M10)>点防御属性. 或者lua callscriptex(actor,“LINKPICKUPITEM”) callscriptex(…...

【Mac】安装Gradle
1、说明 Gradle 运行依赖 JVM,需要先安装JDK,Gradle 与 JDK的版本对应参见:Java Compatibility IDEA的版本也是有要求Gradle版本的,二者版本对应关系参见:Third-Party Software and Licenses 本次 Gradle 安装版本为…...

MySQL中的redoLog
在数据库系统中,redo log(重做日志)用于记录所有已提交事务的修改操作,它的主要目的是确保在系统崩溃或故障后,能够恢复数据库到崩溃前的状态。Redo log 记录的是事务修改的数据的具体操作,而不是数据本身。…...

Windows 安装 MySQL
1.下载 MySQL 安装包 访问:MySQL :: Download MySQL Installer选择适合的版本。推荐下载 MySQL Installer for Windows,该安装包包含所有必要的组件选择 Windows (x86, 32-bit), MSI Installer 或 Windows (x86, 64-bit), MSI Installer 2.运行安装程序…...

yocto的xxx.bb文件在什么时候会拷贝文件到build目录
在 Yocto 中,.bb 文件用于描述如何构建和安装一个软件包,而文件在构建过程中的拷贝操作通常会在某些特定的步骤中进行。具体来说,文件会在以下几个阶段被拷贝到 build 目录(或者更准确地说,拷贝到目标目录 ${D}&#x…...