欲看此文,必先可先看:
golang實現併發爬蟲一(單任務版本爬蟲功能)
gollang實現併發爬蟲二(簡單排程器)
上文中的用簡單的排程器實現了併發爬蟲。
並且,也提到了這種併發爬蟲的實現可以提高爬取效率。
當workercount為1和workercount為10時其爬取效率是有明顯不同的。
然而,文末其實也提到了這個簡單排程器實現的爬蟲有個不可控或者說是控制力太小了的問題。
究其原因就是因為我們實現的方法是來乙個request就給建立乙個groutine。
為了讓這個程式變得更為可控。得想想怎麼可以優化下了。
現在,非常明顯,優化點就是我不想要來乙個request就建立乙個這個實現過程。
那麼,我們可以想到佇列。
把request放到request佇列裡。
那麼,request佇列裡一定是會有乙個request的頭的,我們就可以把這個request的頭元素給到worker去做實現。
也就是這樣:
but,這樣是沒有對worker進行乙個控制的。
我們希望request可以選擇我們想要的乙個worker。
那麼,我們也可以讓scheduler維護乙個worker的佇列。
這裡用了三個並行的模組:
1.engine 引擎模組。
2.scheduler 排程器模組。
3.worker 工作模組。
這三者通訊都是通過channel來通訊的。
上圖中可知道排程器模組實際上是維護了2個channel,乙個是request的channel,乙個是worker的channel。
//那麼,我們就只需要在這個scheduler排程器的兩個channel裡,各取乙個元素,即取request和worker(chan con_engine.request),把request發給worker就可以了。佇列排程器
//這個scheduler與engine和worker之間的通訊都是通過channel來連線的。
//故爾它的肚子裡應該有request相關的channel和worker相關的channel.
//另外注意這裡worker的channel的型別是chan request。
type queuedscheduler struct
一直不斷的去取和傳送,這就是這個佇列排程器要做的事情了。
那個彎曲的箭頭也就是指的這個事情了。在request的佇列裡找到合適的request發給worker佇列裡合適的worker就好。
這就是乙個整體的思想了。
稍微說下關於維護如何兩個佇列的**。
重點在於怎麼才能做到各讀取乙個元素。
channel的讀取是會阻塞的。
如果我先讀取request,如果讀取不到,那麼在等待的時候就沒有辦法取到worker了。
解決方案就是用select,因為select會保證一點,select裡的每乙個case都會被執行到且會很快速的執行。
func (s *queuedscheduler) run()select就是在做三件事情://收到乙個request就讓request排隊,收到乙個worker就讓worker排隊。所有的channel操作都放到select裡。
select
}}()
}
1.從requestchan裡收乙個request,將這個request存在變數requestq裡。
2.從workerchan裡收乙個worker,將這個worker存在變數workerq裡。
3.把第乙個requestq裡的第乙個元素發給第乙個workerq裡的第乙個元素。
其他**就感興趣的同學自己看吧。
作者就先說到這裡。
總體排程的思想上面的圖中。
具體的實現在原始碼裡。
原始碼:
Go Web爬蟲併發實現
題目 exercise web crawler 直接參考了 的實現,不過該 使用了chan bool來存放子協程是否執行完成,我的 是使用waitgroup來讓主協程等待子協程執行完成。完整 請參考 相對原程式增加的 如下 var fetched struct crawl uses fetcher ...
Golang實現併發質數篩選法
質數篩選法 埃拉託斯特尼篩法 是在乙個尋找給定範圍內最大質數的古老演算法。它通過一定的順序篩掉多個質數的乘積,最終得到想要的最大質數。這個演算法的並行版本定義了多個 goroutine,每個 goroutine 代表乙個已經找到的質數,同時有多個 channel 用來從 generator 傳輸資料...
Golang實現對map的併發讀寫
在golang多協程的情況下使用全域性map時,如果不做執行緒同步,會出現panic的情況。為了解決這個問題,通常有兩種方式 寫了乙個模擬程式對map中的一項進行讀或者寫,後台一直執行的協程阻塞的接受讀寫訊號,並對map進行操作,但是讀操作的時候沒想好怎麼返回這個值。後來想到用傳引用的方式,定義結構...