侧边栏壁纸
博主头像
落叶人生博主等级

走进秋风,寻找秋天的落叶

  • 累计撰写 130562 篇文章
  • 累计创建 28 个标签
  • 累计收到 9 条评论
标签搜索

目 录CONTENT

文章目录

记webmagic一个多线程问题排查和修复的过程

2022-06-28 星期二 / 0 评论 / 0 点赞 / 65 阅读 / 3872 字

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

在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不要靠猜!一定要复现并确认解决!

广告 广告

评论区