併發程式設計要考慮的幾點
併發程式設計的目的是為了讓程式執行的更快,但是,並不是啟動更多的執行緒就能讓程式最大限度的併發執行。在進行併發程式設計時,如果希望通過多執行緒執行任務讓程式執行的更快,會面臨很多的問題,比如,上下文切換,死鎖的問題,以及受限於硬體和軟體的資源限制的問題。
1.1上下文切換
即使是單執行緒處理器也支援多執行緒執行程式碼,CPU通過給每個執行緒分配CPU時間片來實現這個機制。時間片是CPU分配給各個執行緒的時間,因為時間片很短,所以CPU通過不斷的切換執行緒執行,讓我們感到多個執行緒是同時執行的,時間片一般是幾十毫秒。CPU通過時間片分配演算法來迴圈執行任務,當前任務執行一個時間片後會切換到下一個任務。但是,在切換前會儲存上一個任務的狀態,以便下次切換回這個任務時,可以在載入這個任務的狀態。所以任務從儲存在載入的過程就是一次上下文切換。
然而並不是多執行緒,程式就一定執行的快。在考慮計算機是多核CPU的情況下,當併發執行累加不超過百萬次時,速度會比序列執行累加操作要慢,那麼,為什麼併發執行的速度會比序列的慢呢,只是因為執行緒有建立和上下文切換的開銷。通過程式測試可以得出結論,上下文每1秒切換1000多次。所以進行一次上下文的切換的時間開銷大致在1毫秒,這樣的開銷是非常大的。
如何減小上下文切換:
減小上下文切換的方法有無鎖併發程式設計,CAS演算法,使用最少執行緒和使用協程。
- 無鎖併發程式設計。多執行緒競爭鎖時,會引起上下文的切換,所以多執行緒處理資料時,可以用一些方法來避免使用鎖,如將資料的ID按照Hash演算法取模分段,不同的執行緒處理不同段的資料。
- CAS演算法。Java的Atomic包使用CAS演算法來更新資料,而不需要加鎖。
- 使用最少執行緒。避免建立不需要的執行緒,比如任務很少,但是建立了許多執行緒來處理,這樣會造成大量執行緒都處於等待狀態。
- 協程。在單執行緒裡實現多工的排程,並在單執行緒裡維持多個任務間的切換。
1.2死鎖
鎖是一個非常有用的工具,運用的場景非常之多,但是也有的情況下會產生一些問題,這就是死鎖。所謂死鎖想必大家也都知道,簡單的說就是多個執行緒相互等待其他執行緒釋放佔用的資源之後才能釋放自己的資源,從而導致各個執行緒之間相互等待的嚴重問題。
這裡我們只是簡單的說一下避免死鎖的幾種方法:
- 避免一個執行緒同時獲取多個鎖;
- 避免一個執行緒在鎖內同時佔用多個資源,儘量保證每個鎖只佔用一個資源。
- 嘗試使用定時鎖,使用lock.tryLock(timeout)來代替使用內部鎖機制。
- 對於資料庫鎖,加鎖和解鎖必須在一個數據庫連線裡,否則會出現解鎖失敗的情況。
1.3資源限制的挑戰
1、什麼是資源限制
資源限制:是指在進行併發程式設計時,程式的執行速度受限於計算機硬體資源或者軟體資源。比如,伺服器的頻寬是2Mb/s,某個資源的下載速度是1Mb/s,系統啟動10個執行緒下載資源,下載的速度不會變成10Mb/s,所以在進行併發程式設計時,要考慮這些資源的限制。硬體資源限制有寬頻的上傳下載速度,硬碟的讀寫速度和CPU的處理速度。軟體資源限制有資料庫的連結數和socket連結數。
2、資源限制引發的問題
在併發程式設計中,將程式碼執行速度加快的原則是將程式碼中序列執行的部分變成併發執行,但是如果將某段序列的程式碼併發執行,因為受限於資源,仍然在序列執行,這個時候程式不僅僅不會加快執行,反而會更慢,因為增加了上下文的切換和資源排程的時間。
3、如何解決組員限制的問題
對於硬體資源受限,可以考慮使用叢集併發執行程式。既然單機的資源有限制,那麼就讓程式在多機上執行。對於軟體資源限制,可以考慮使用資源池將資源複用,比如,使用連線池將資料庫和socket連線複用,或者在呼叫對方webservice介面獲取資料時,只建立一個連線。
4、在資源限制情況下進行併發程式設計
根據不同的資源限制調整程式的併發度,比如下載檔案程式依賴於兩個資源--頻寬和硬碟讀寫速度。有資料庫操作時,涉及資料庫連線數,如果sql語句執行非常快,而執行緒的數量比資料庫連線數大得多,則某些執行緒會被阻塞,等待資料庫連線