zoukankan      html  css  js  c++  java
  • 当Scheduler拿不到url的 时候,不能立即退出

    在webmagic的多线程抓取中有一个比较麻烦的问题:当Scheduler拿不到url的 时候,不能立即退出,需要等到没抓完的线程都运行完毕,没有新url产生时,才能退出。之前使用Thread.sleep来实现,当拿不到url 时,sleep一段时间再取,确定没有线程执行之后,再退出。

    但是这种方式始终不够优雅。Java里面有wait/notify机制可以解决这种同步的问题。于是webmagic 0.4.0用wait/notify机制代替了之前的Thread.sleep机制。代码如下:

        while (!Thread.currentThread().isInterrupted() && stat.get() == STAT_RUNNING) {
            Request request = scheduler.poll(this);
            if (request == null) {
                if (threadAlive.get() == 0 && exitWhenComplete) {
                    break;
                }
                // wait until new url added
                waitNewUrl();
            } else {
                final Request requestFinal = request;
                threadAlive.incrementAndGet();
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            processRequest(requestFinal);
                        } catch (Exception e) {
                            logger.error("download " + requestFinal + " error", e);
                        } finally {
                            threadAlive.decrementAndGet();
                            signalNewUrl();
                        }
                    }
                });
            }
        }
    
    private void waitNewUrl() {
        try {
            newUrlLock.lock();
            try {
                newUrlCondition.await();
            } catch (InterruptedException e) {
            }
        } finally {
            newUrlLock.unlock();
        }
    }
    

    这里当线程完成之后,会调用signalNewUrl()来通知主线程,停止等待!

    0.4.0发布之后,有用户问我,为什么有的时候抓完无法退出?我开始就怀疑这里可能存在线程安全问题,但是苦于无法复现。

    思考了一下,有可能存在这样执行情况:

    1. threadAlive>0,执行if (threadAlive.get() == 0 && exitWhenComplete)check跳过,于是准备进入waitNewUrl();
    2. 此时最后一个子线程执行结束,threadAlive.decrementAndGet();signalNewUrl();相继执行;
    3. 此时主线程进入waitNewUrl(),结果已无线程执行,也无人可以notify它了,于是线程一直等待…

    那么似乎在lock里加入double-check就OK了?但是今天看了http://coolshell.cn/articles/4576.html这篇文章,大概意思是:出了问题不要靠猜!一定要复现并测试!

    于是决定手动模拟!开启10个线程,并mock了所有部件,循环10000次去执行,代码不贴了,地址:https://github.com/code4craft/webmagic/blob/master/webmagic-core/src/test/java/us/codecraft/webmagic/SpiderTest.java。执行一下,果然到了第13次就卡住了!jstack之后,果然卡在newUrlCondition.await();这里!

    然后加入double-check:

    private void waitNewUrl() {
        try {
            newUrlLock.lock();
            //double check
            if (threadAlive.get() == 0 && exitWhenComplete) {
                return;
            }
            try {
                newUrlCondition.await();
            } catch (InterruptedException e) {
            }
        } finally {
            newUrlLock.unlock();
        }
    }
    

    结果执行成功!至此问题解决!

    经过这个例子,也大致明白了为什么wait/notify之前总是要先lock。为什么呢?有机会写一篇文章总结一下吧!

    很简单,是吧?其实这篇文章只想说明一件事:出了bug不要靠猜!一定要复现并确认解决!

  • 相关阅读:
    spring 整合 shiro框架
    Kafka常见问题及解决方法
    设计模式之解释器模式规则你来定(二十五)
    设计模式之原型模式简单即复杂(二十四)
    设计模式之访问者模式层次操作(二十三)
    设计模式之状态模式IFORNOIF(二十二)
    设计模式之职责链模式永不罢休(二十一)
    设计模式之组合模式透明实用(二十)
    设计模式之享元模式高效复用(十九)
    设计模式之迭代器模式解析学习源码(十八)
  • 原文地址:https://www.cnblogs.com/timssd/p/5149660.html
Copyright © 2011-2022 走看看