wait必须放在while循环里面的原因探析

本文通过一个Java示例程序展示了并发编程中wait和notify的使用方式及注意事项。特别指出,当多个线程等待同一资源时,仅使用if判断可能导致的异常情况,并提出了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先看一段代码:

    import java.util.*;  
      
    public class EarlyNotify extends Object {  
        private List list;  
      
        public EarlyNotify() {  
            list = Collections.synchronizedList(new LinkedList());  
        }  
      
        public String removeItem() throws InterruptedException {  
            print("in removeItem() - entering");  
      
            synchronized ( list ) {  
                if ( list.isEmpty() ) {  //这里用if语句会发生危险  
                    print("in removeItem() - about to wait()");  
                    list.wait();  
                    print("in removeItem() - done with wait()");  
                }  
      
                //删除元素  
                String item = (String) list.remove(0);  
      
                print("in removeItem() - leaving");  
                return item;  
            }  
        }  
      
        public void addItem(String item) {  
            print("in addItem() - entering");  
            synchronized ( list ) {  
                //添加元素  
                list.add(item);  
                print("in addItem() - just added: '" + item + "'");  
      
                //添加后,通知所有线程  
                list.notifyAll();  
                print("in addItem() - just notified");  
            }  
            print("in addItem() - leaving");  
        }  
      
        private static void print(String msg) {  
            String name = Thread.currentThread().getName();  
            System.out.println(name + ": " + msg);  
        }  
      
        public static void main(String[] args) {  
            final EarlyNotify en = new EarlyNotify();  
      
            Runnable runA = new Runnable() {  
                    public void run() {  
                        try {  
                            String item = en.removeItem();  
                            print("in run() - returned: '" +   
                                    item + "'");  
                        } catch ( InterruptedException ix ) {  
                            print("interrupted!");  
                        } catch ( Exception x ) {  
                            print("threw an Exception!!!\n" + x);  
                        }  
                    }  
                };  
      
            Runnable runB = new Runnable() {  
                    public void run() {  
                        en.addItem("Hello!");  
                    }  
                };  
      
            try {  
                //启动第一个删除元素的线程  
                Thread threadA1 = new Thread(runA, "threadA1");  
                threadA1.start();  
      
                Thread.sleep(500);  
          
                //启动第二个删除元素的线程  
                Thread threadA2 = new Thread(runA, "threadA2");  
                threadA2.start();  
      
                Thread.sleep(500);  
                //启动增加元素的线程  
                Thread threadB = new Thread(runB, "threadB");  
                threadB.start();  
      
                Thread.sleep(10000); // wait 10 seconds  
      
                threadA1.interrupt();  
                threadA2.interrupt();  
            } catch ( InterruptedException x ) {}  
        }  
    }  

输出结果:
threadA1: in removeItem() - entering
threadA1: in removeItem() - about to wait()
threadA2: in removeItem() - entering
threadA2: in removeItem() - about to wait()
threadB: in addItem() - entering
threadB: in addItem() - just added: 'Hello!'
threadB: in addItem() - just notified
threadA2: in removeItem() - done with wait()
threadA2: in removeItem() - leaving
threadA2: in run() - returned: 'Hello!'
threadA1: in removeItem() - done with wait()
threadA1: threw an Exception!!!
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
threadB: in addItem() - leaving

原因分析:

首先启动threadA1,threadA1在removeItem()中调用wait(),从而释放list上的对象锁。再过500ms,启动threadA2,threadA2调用removeItem(),获取list上的对象锁,也发现列表为空,从而在wait()方法处阻塞,释放list上的对象锁。再过500ms后,启动threadB,并调用addItem,获得list上的对象锁,并在list中添加一个元素,同时用notifyAll通知所有线程。

    threadA1和threadA2都从wait()返回,等待获取list对象上的对象锁,并试图从列表中删除添加的元素,这就会产生麻烦,只有其中一个操作能成功。假设threadA1获取了list上的对象锁,并删除元素成功,在退出synchronized代码块时,它便会释放list上的对象锁,此时threadA2便会获取list上的对象锁,会继续删除list中的元素,但是list已经为空了,这便会抛出IndexOutOfBoundsException。

解决办法:

将if换为while。重新判断是否为空,为空时继续等待挂起。

放在while里面,是防止出于waiting的对象被别的原因调用了唤醒方法,但是while里面的条件并没有满足(也可能当时满足了,但是由于别的线程操作后,又不满足了),就需要再次调用wait将其挂起。

好笑的解释:

如果是两个狙击手,在同时等待一个人,第一个人先打死了(改变了某个状态,不满足了条件了),第二个人还要再开一枪?针对两个线程同时等待还是要加这个while的。

<think>好的,用户想将IO0的触发启动功能从PIO移到主循环中。我需要先理解现有的代码结构。原代码中,PIO程序clock_gen使用了等待IO0低电平触发的指令,然后生成时钟脉冲。用户希望把这个触发检测部分移到Arduino的主循环loop()函数。 首先,我需要确认PIO程序的作用。原来的clock_gen在PIO等待触发信号,现在要移除这个逻辑。所以需要修改PIO程序,让它不再等待触发,而是持续生成时钟信号。这样主循环可以控制何时启动采样。 然后,主循环中需要检测IO0的触发信号。原来的loop()函数已经有一个检测触发引脚的逻辑,但可能需要在检测到触发后启动PIO的状态机或者DMA传输。可能需要配置PIO状态机在触发时开始运行,或者通过某种方式启动数据采集。 接下来要考虑的是时序问题。原来的PIO代码在检测到触发后立即生成脉冲,现在改为在主循环中检测触发,可能会有延迟。需要确保触发检测的响应时间足够快,不会影响数据采集的准确性。 另外,DMA的配置是否需要调整?原来的代码中使用双缓冲,触发时切换缓冲区并启动DMA传输。现在触发检测移到主循环,可能需要确保在触发时正确启动DMA,并处理数据缓冲区。 还需要注意PIO状态机的启动和停止。在原来的设计中,PIO可能是一直运行的,但现在触发控制可能在主循环中,可能需要手动启动或重新初始化状态机。 需要检查PIO程序的修改是否正确,比如移除等待触发的指令,并调整脉冲生成的循环。同时,确保主循环中的触发检测逻辑可靠,比如可能需要添加去抖或确认触发电平稳定的措施。 最后,测试和验证。修改后的代码需要测试触发功能是否正常工作,时钟信号是否正确生成,数据采集是否在触发时启动,并且数据没有丢失或错位。 总结步骤:修改PIO程序移除触发等待,调整主循环中的触发检测逻辑,确保DMA正确配置和启动,验证时序和功能正确性。</think>以下是修改后的实现方案,将触发检测移到主循环中,同时保持PIO生成时钟信号: ```cpp #include <Arduino.h> #include <hardware/pio.h> #include <hardware/dma.h> // 硬件定义 #define CLOCK_PIN 1 // IO1输出时钟/脉冲 #define TRIGGER_PIN 0 // IO0作为触发输入 #define ADC_DATA_BASE 2 // AD9220数据线起始引脚 #define ADC_CLK_PIN 12 // ADC采样时钟引脚 // 修改后的PIO程序(移除触发等待) static const char clock_gen_program[] = R"( .program clock_gen .wrap_target set pins, 1 ; 初始高电平 set pins, 0 [19] ; 保持20周期低电平(100ns) set pins, 1 .wrap )"; static const char adc_reader_program[] = R"( .program adc_reader .side_set 1 opt .wrap_target in pins, 12 side 0 ; 读取12位数据 nop side 1 ; 产生时钟上升沿 .wrap )"; // DMA双缓冲配置 #define BUF_SIZE 20000 uint16_t __attribute__((aligned(32))) buffer[2][BUF_SIZE]; int current_buffer = 0; int dma_chan; volatile bool capture_flag = false; // 新增触发标志 void setup() { // 初始化触发引脚 gpio_init(TRIGGER_PIN); gpio_set_dir(TRIGGER_PIN, GPIO_IN); gpio_pull_up(TRIGGER_PIN); // 加载时钟生成PIO程序(持续运行) PIO pio0 = pio0; uint offset0 = pio_add_program(pio0, &clock_gen_program); pio_sm_config c0 = pio_get_default_sm_config(); sm_config_set_wrap(&c0, offset0, offset0 + 2); // 调整wrap范围 sm_config_set_set_pins(&c0, CLOCK_PIN, 1); sm_config_set_clkdiv_int_frac(&c0, 19, 0); // 10MHz时钟 pio_sm_set_pins_with_mask(pio0, 0, 1u << CLOCK_PIN); pio_sm_init(pio0, 0, offset0, &c0); pio_sm_set_enabled(pio0, 0, true); // 加载ADC读取PIO程序(保持停止状态) PIO pio1 = pio1; uint offset1 = pio_add_program(pio1, &adc_reader_program); pio_sm_config c1 = pio_get_default_sm_config(); sm_config_set_wrap(&c1, offset1, offset1 + 2); sm_config_set_in_pins(&c1, ADC_DATA_BASE); sm_config_set_sideset_pins(&c1, ADC_CLK_PIN); sm_config_set_clkdiv_int_frac(&c1, 19, 0); // 10MHz采样时钟 pio_sm_init(pio1, 0, offset1, &c1); pio_sm_set_enabled(pio1, 0, false); // 初始禁用 // 配置DMA dma_chan = dma_claim_unused_channel(true); dma_channel_config cfg = dma_channel_get_default_config(dma_chan); channel_config_set_transfer_data_size(&cfg, DMA_SIZE_16); channel_config_set_read_increment(&cfg, false); channel_config_set_write_increment(&cfg, true); channel_config_set_dreq(&cfg, pio_get_dreq(pio1, 0, false)); } void loop() { // 检测下降沿触发(带消抖) static uint32_t last_trigger = 0; if (!gpio_get(TRIGGER_PIN) && (millis() - last_trigger > 10)) { last_trigger = millis(); // 切换DMA目标缓冲区 current_buffer ^= 1; dma_channel_set_write_addr(dma_chan, buffer[current_buffer], true); // 启动ADC读取状态机 pio_sm_set_enabled(pio1, 0, true); dma_channel_start(dma_chan); // 等待DMA完成 while (dma_channel_is_busy(dma_chan)) { __wfe(); } // 停止ADC读取状态机 pio_sm_set_enabled(pio1, 0, false); // 数据处理示例 // process_data(buffer[current_buffer ^ 1]); } } ``` ### 主要修改说明: 1. **PIO程序重构**: - 移除`clock_gen`中的`wait 0 pin, 0`指令 - 使时钟生成程序持续运行(无需触发控制) - ADC读取状态机初始处于禁用状态 2. **触发控制优化**: ```cpp // 新增消抖逻辑(10ms间隔) if (!gpio_get(TRIGGER_PIN) && (millis() - last_trigger > 10)) { // 触发后启动采集状态机 pio_sm_set_enabled(pio1, 0, true); // 启动DMA传输 dma_channel_start(dma_chan); } ``` 3. **时序保障措施**: - 通过`pio_sm_set_enabled()`动态控制ADC读取状态机 - 保持时钟生成PIO持续运行确保时序稳定 - 每次触发后重置DMA传输地址 4. **性能优化点**: - 添加10ms触发间隔防止误触发 - 使用`volatile`标志位确保状态同步 - 在DMA传输完成后立即禁用状态机降低功耗 ### 验证建议: 1. 使用逻辑分析仪同时监测: - GPIO0(触发信号) - GPIO1(时钟输出) - GPIO12(ADC采样时钟) 2. 通过以下代码验证触发响应时间: ```cpp void loop() { static uint32_t start; if (触发条件) { start = micros(); // 启动采集 } if (采集完成) { Serial.println(micros() - start); } } ``` 3. 建议添加LED指示灯反馈触发状态: ```cpp #define STATUS_LED 25 void setup() { gpio_init(STATUS_LED); gpio_set_dir(STATUS_LED, GPIO_OUT); } void loop() { if (触发中) { gpio_put(STATUS_LED, 1); } else { gpio_put(STATUS_LED, 0); } } ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值