在webmagic的多線程抓取中有一個比較麻煩的問題:當Scheduler拿不到url的時候,不能當即退出,須要等到沒抓完的線程都運行完畢,沒有新url產生時,才能退出。以前使用Thread.sleep來實現,當拿不到url時,sleep一段時間再取,肯定沒有線程執行以後,再退出。html
可是這種方式始終不夠優雅。Java裏面有wait/notify機制能夠解決這種同步的問題。因而webmagic 0.4.0用wait/notify機制代替了以前的Thread.sleep機制。代碼以下:java
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()
來通知主線程,中止等待!git
0.4.0發佈以後,有用戶問我,爲何有的時候抓完沒法退出?我開始就懷疑這裏可能存在線程安全問題,可是苦於沒法復現。github
思考了一下,有可能存在這樣執行狀況:web
if (threadAlive.get() == 0 && exitWhenComplete)
check跳過,因而準備進入waitNewUrl()
;threadAlive.decrementAndGet();
和signalNewUrl();
相繼執行;waitNewUrl()
,結果已無線程執行,也無人能夠notify它了,因而線程一直等待…那麼彷佛在lock里加入double-check就OK了?可是今天看了http://coolshell.cn/articles/4576.html這篇文章,大概意思是:出了問題不要靠猜!必定要復現並測試!shell
因而決定手動模擬!開啓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(); } }
結果執行成功!至此問題解決!ide
通過這個例子,也大體明白了爲何wait/notify以前老是要先lock
。爲何呢?有機會寫一篇文章總結一下吧!測試
很簡單,是吧?其實這篇文章只想說明一件事:出了bug不要靠猜!必定要復現並確認解決!