【问题标题】:Strange behavior from a while loop来自while循环的奇怪行为
【发布时间】:2015-04-19 22:07:22
【问题描述】:

所以我正在写一个视频播放库,细节并不重要。这是怎么回事,这段代码需要在解码器线程的run()方法结束时运行:

System.out.println("Video decoding complete");
int a = 0, b = 0;
do
{
    a = pictures.getCount();
    b = samples.getCount();
}while(a > 0 || b > 0);
Gdx.app.log("Status", videoPath + " completed playing successfully.");
videoComplete = true;

问题是,通过 do{}while 的任何内容都不会执行。{}这是奇怪的部分,这段代码是在将 System.out.println 添加到 while 循环中时执行的:

System.out.println("Video decoding complete");
int a = 0, b = 0;
do
{
    System.out.println("Waiting for packets to drain.");
    a = pictures.getCount();
    b = samples.getCount();
}while(a > 0 || b > 0);
Gdx.app.log("Status", videoPath + " completed playing successfully.");
videoComplete = true;

我怀疑编译器知道我试图让它运行一个循环,它暂时什么都不做,它只是剪掉了代码或其他东西。但我真的不知道发生了什么。如果有人比我更了解,我希望有更好的解决方案。这么简单的事情,我就挂在这里了!

【问题讨论】:

  • 不,当编译器改变程序的行为时,它永远不会删除代码位。你怎么知道他们没有被处决?
  • 一开始为什么要“让它运行一个不做任何事情的循环”?
  • 我不知道您所看到的原因,但您所拥有的是一个“忙等待”,它将占用 CPU 直到计数达到零。阅读线程同步,并使其 wait() 用于其他线程。
  • 贴一个完整的例子,我们可以重现,否则很难调试。
  • @slim - 是的,好电话。下面的答案有效,但我最终接受了你的建议。 :D

标签: java multithreading compiler-optimization


【解决方案1】:

我的猜测是 pictures.getCount() 和 samples.getCount() 读取非volatile 字段。当您只读取非易失性字段时,出于性能原因,它可以被内联,但是如果您执行诸如调用synchronized 方法之类的操作(并且 System.out 是同步的),它不会以这种方式优化代码并且必须执行每次查找。

我建议你尝试添加一个空的同步块,看看这是否仍然有效,即这样做而不是 println

synchronized(this) { }

【讨论】:

  • 为什么添加 System.out.println("Waiting for packets to drain."); 会有什么不同?
  • 可能通过在 IO 发生时添加一个微小的暂停,在此期间传播非易失性字段。
  • 让我试一试。我一直避免使用同步,因为它与我的 openGL 线程混淆,但我认为这应该是安全的。您对非易失性字段的看法是正确的,openGL 的破坏就是原因。
  • @pbabcdefp 就像我说的,当代码包含同步方法时,它保证字段的可行性并防止此类优化。
  • 抱歉。我刚刚意识到你确实在你的回答(+1)中解决了这个问题。时间不早了。
【解决方案2】:

我最终按照@slim 的建议这样做了:

                System.out.println("Video decoding complete");
                this.decoderComplete = true;
                //wait until notified that packets are done draining
                synchronized(this)
                {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        videoComplete = true;
                        this.container.close();
                        e.printStackTrace();
                    }
                }
                Gdx.app.log("Status", videoPath + " completed playing successfully.");
                videoComplete = true;
                this.container.close();

在另一个线程中,一旦我们知道我们已经完成了所有数据包的读取:

if(this.packetHandlerRunnable.getNumAudioPackets() <= 0 
                && this.packetHandlerRunnable.getNumVideoPackets() <= 0 
                && this.packetHandlerRunnable.isDecoderComplete())
        {
            synchronized(packetHandlerRunnable)
            {
                this.packetHandlerRunnable.notify();
            }
        }

【讨论】:

  • 值得记住的是等待/通知不是一个可靠的信号。等待必须在一个循环中,该循环使用通知检查代码中设置的状态。如果您不这样做 a) 通知可能会丢失或 b) 等待虚假地完成。
  • 此外,您仍然需要检查同步块中的变量以避免问题中的问题。
  • @PeterLawrey - 所以解码器线程可以关闭它正在使用的对象。那里有必须手动清理的 JNI 库。好问题。
  • 感谢大家的意见!这很有启发性:D
  • 它会给你一个变量让服务员检查它的循环。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多