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

时间: 2024-08-24 01:30:19

当Scheduler拿不到url的 时候,不能立即退出的相关文章

asp.net mvc url应用

//url加密与解密string res1 = HttpUtility.UrlEncode("7Z2K5Lgk/iI="); //值是7Z2K5Lgk%2fiI%3d string res2 = HttpUtility.UrlEncode(res1); //值是: 7Z2K5Lgk%252fiI%253d string res3 = HttpUtility.UrlDecode(res2); //值是7Z2K5Lgk%2fiI%3d string res4 = HttpUtility.U

scrapy-redis使用以及剖析

scrapy-redis是一个基于redis的scrapy组件,通过它可以快速实现简单分布式爬虫程序,该组件本质上提供了三大功能: scheduler - 调度器 dupefilter - URL去重规则(被调度器使用) pipeline   - 数据持久化 scrapy-redis组件 1. URL去重 定义去重规则(被调度器调用并应用) a. 内部会使用以下配置进行连接Redis # REDIS_HOST = 'localhost' # 主机名 # REDIS_PORT = 6379 # 端

scrapy-redis实现爬虫分布式爬取分析与实现

一 scrapy-redis实现分布式爬取分析 所谓的scrapy-redis实际上就是scrapy+redis其中对redis的操作采用redis-py客户端.这里的redis的作用以及在scrapy-redis的方向我在自己fork的repository(链接:)已经做了翻译(README.rst). 在前面一篇文章中我已经借助两篇相关文章分析了使用redis实现爬虫分布式的中心.归结起来就是:所有爬虫获取到的url(request)都放到一个redis queue中,并且所有爬虫都从单个r

(5)Quartz学习

原文:http://blog.csdn.net/zxl315/article/details/10879927 介绍Quartz Quartz是一个开源的任务调度系统,它能用来调度很多任务的执行. 运行环境 Quartz 能嵌入在其他应用程序里运行. Quartz 能在一个应用服务器里被实例化(或servlet容器), 并且参与XA事务 Quartz能独立运行(通过JVM),或者通过RMI Quartz能被集群实例化 任务调度 当一个指定给任务的触发器发生时,任务就被调度执行. 触发器能被创建为

Python爬虫抓取csdn博客

昨天晚上为了下载保存某位csdn大牛的全部博文,写了一个爬虫来自动抓取文章并保存到txt文本,当然也可以 保存到html网页中.这样就可以不用Ctrl+C 和Ctrl+V了,非常方便,抓取别的网站也是大同小异. 为了解析抓取的网页,用到了第三方模块,BeautifulSoup,这个模块对于解析html文件非常有用,当然也可以自己使用正则表达式去解析,但是比较麻烦. 由于csdn网站的robots.txt文件中显示禁止任何爬虫,所以必须把爬虫伪装成浏览器,而且不能频繁抓取,得sleep一会再抓,使

scrapy+redis组件

scrapy-redis插件:实现分布式爬虫. scheduler - 调度器 dupefilter - URL去重规则(被调度器使用) pipeline   - 数据持久化 pip3 install scrapy-redis 一,url去重 定义去重规则(被调度器调用并应用) a. 内部会使用以下配置进行连接Redis # REDIS_HOST = 'localhost' # 主机名 # REDIS_PORT = 6379 # 端口 # REDIS_URL = 'redis://user:[e

scripy redis

scrapy-redis是一个基于redis的scrapy组件,通过它可以快速实现简单分布式爬虫程序,该组件本质上提供了三大功能: scheduler - 调度器 dupefilter - URL去重规则(被调度器使用) pipeline   - 数据持久化 scrapy-redis组件 1. URL去重 a a. 利用scrapy-redis做去重规则: # ############ 连接redis 信息 ################# REDIS_HOST = '127.0.0.1' #

scrapy-redis的使用与解析

scrapy-redis是一个基于redis的scrapy组件,通过它可以快速实现简单分布式爬虫程序,该组件本质上提供了三大功能: scheduler - 调度器 dupefilter - URL去重规则(被调度器使用) pipeline   - 数据持久化 scrapy-redis组件 1. URL去重 定义去重规则(被调度器调用并应用) a. 内部会使用以下配置进行连接Redis # REDIS_HOST = 'localhost' # 主机名 # REDIS_PORT = 6379 # 端

爬虫 之 scrapy-redis组件

scrapy-redis组件 scrapy-redis是一个基于redis的scrapy组件,通过它可以快速实现简单分布式爬虫程序,该组件本质上提供了三大功能: scheduler - 调度器 dupefilter - URL去重规则(被调度器使用) pipeline   - 数据持久化 基于scrapy-redis的去重规则 方案 #- 完全自定义 from scrapy.dupefilter import BaseDupeFilter import redis from scrapy.uti