爬蟲的併發控制:python
多進程、多線程、協程 yield程序員
從硬件:redis
雙核四線程(超線程技術):
有兩個CPU核心,每一個核心有兩個邏輯處理器,至關於有四個CPU核心數據庫
四核四線程:
有一個CPU核心,每一個核心有一個邏輯處理器,至關於有四個CPU核心設計模式
從操做系統:緩存
進程和線程,都是CPU任務的執行單位。安全
進程:早期的操做系統是面向進程的:
表示一個程序的執行活動(打開、執行、保存、關閉)網絡
線程:如今的操做系統都是面向線程:
表示一個進程處理任務時最小調度單位(執行功能a、執行功能b)多線程
一個程序至少開啓一個進程,一個進程至少有一個線程。併發
每一個進程都有獨立的內存空間,不一樣進程之間不共享任何狀態。
進程之間的通訊須要通過操做系統調度控制,通信效率低、切換開銷大。
同一個進程裏的多個線程,是共享內存空間,切換開銷小,通信效率高。
線程的工做機制是"搶佔式",出現競爭的狀態,競爭意味着數據不安全。
引入了"互斥鎖":讓多個線程安全有序的訪問內存空間的機制。
Python的多線程:
相似於 GIL(全局解釋器鎖):保證一個時間片裏只有一個線程在運行。
好處:直接杜絕了多個線程的競爭問題:
壞處:Python的多線程不是真正的多線程。
Python解釋器在處理IO阻塞類型的方法時,會釋放GIL
若是沒有IO操做,該線程會每隔 sys.getcheckinterval() 次釋放GIL,讓其餘線程嘗試執行。
並行:
同一CPU時間片內,CPU能夠同時處理多個程序。若是有多個程序,同步執行。
程序1:----------------
程序2:----------------
程序3:----------------
程序4:----------------
併發:
同一CPU時間片,只能處理一個程序。若是有多個程序,交替執行。
程序1: ----- ------
程序2: -----
程序3: ----
程序4: -----
多進程:能夠充分利用多核CPU的資源,適用於密集CPU任務(大量的並行運算)
Python的多進程模塊:multiprocessing
進程之間通訊成本高切換開銷大,不適用於須要大量數據通訊和切換的任務(爬蟲)
設計模式:生產者消費者(並行模式)
多線程:適用於密集I/O任務(磁盤IO,內存IO,網絡IO),切換開銷小通訊成本低。
Python的多線程:Thread、thraeding、multiprocessing.dummy
多線程:同一個時間片只能執行一個線程,沒法充分利用CPU多核資源(只能作到併發,不能作到並行)
協程:操做系統和CPU不認識協程,是由程序員經過代碼邏輯控制。
特色是在單線程下執行多個任務,且不須要經過操做系統切換(沒有切換開銷,也不須要處理鎖),執行效率高。
Python: gevent ,猴子補丁(Python代碼在執行網絡IO阻塞時,會自動切換協程)
協程:適用於密集網絡I/O任務
多進程爬蟲:不合適
多線程爬蟲:缺點 - 經過操做系統調度,有線程切換開銷(海量URLs場景會增長CPU負載);優勢 - 使用場景普遍(網絡讀寫併發/數據庫讀寫併發/磁盤讀寫併發)
協程爬蟲:缺點 - gevent配合monkey.patch_all() 只能提升網絡併發效率,不能處理其餘併發場景;優勢 - 經過程序員代碼邏輯控制,不受操做系統調度,沒有切換開銷,下降CPU負載(處理海量URLs優點明顯)
執行方式:
同步:執行一個任務,必須等待上一個任務完成(沒有併發的爬蟲)
異步:執行一個任務,沒必要等待上一個任務完成(併發的爬蟲)
程序狀態:
阻塞:程序執行時,必須等待該任務完成,不然保持等待狀態。
非阻塞:程序執行時,沒必要等待該任務完成,能夠繼續執行下一個任務。
異步+非阻塞(效率最高):
發送請求後,沒必要等待響應返回,能夠繼續處理其餘請求發送;當處理功能掛起時,可以馬上切換其餘到功能繼續執行。
異步網絡框架 Twisted Tornada
Scrapy :請求處理模塊+響應解析模塊+twisted
scrapy-redis:Scrapy + Redis(在同一個數據庫裏處理請求去重、請求分配、數據存儲)
單機爬蟲:
分佈式爬蟲:
CPU -> 寄存器 -> CPU緩存L1/L2/L3 -> 內存 -> 硬盤/固態硬盤 -> 網絡
協程:切換
三種方式實現: 1. yield 2.greenlet 3. gevent
1.yield
做用:
掛起當前函數 將後面表達式的值 返回到調用生成器的地方
接收數據 並喚醒當前函數 而且緊接着上次運行的地址繼續執行
2.greenler
greenlet(函數) 建立協程
gr2.switch()切換到gr2執行
協程是python箇中另一種實現多任務的方式,只不過比線程更小佔用更小執行單元(理解爲須要的資源)
通俗的理解:在一個線程中的某個函數,能夠在任何地方保存當前函數的一些臨時變量等信息,而後切換到另一個函數中執行,注意不是經過調用函數的方式作到的,而且切換的次數以及何時再切換到原來的函數都由開發者本身肯定
建立並執行協程
阻塞等待協程運行完成 .join()
阻塞等待全部協程退出 .join_all()
monkey.patch_all()做用是將rece, recefrom, time.sleep, accept進行破解, 不會阻塞等待, 在調用時能夠切換到別的任務繼續執行.
注意:join,join_all做用保持主進程存活
簡單總結