記webmagic一個多線程問題排查和修復的過程

在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

  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這篇文章,大概意思是:出了問題不要靠猜!必定要復現並測試!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不要靠猜!必定要復現並確認解決!

相關文章
相關標籤/搜索