摘要:并發表示在一段時間內有多個動作存在。并發帶來的問題在享受并發編程帶來的高性能高吞吐量的同時,也會因為并發編程帶來一些意想不到弊端。并發過程中多線程之間的切換調度,上下文的保存恢復等都會帶來額外的線程切換開銷。
0x01 什么是并發
要理解并發首選我們來區分下并發和并行的概念。
并發:表示在一段時間內有多個動作存在。
并行:表示在同一時間點有多個動作同時存在。
例如:
此刻我正在寫博客,但是我寫著寫著停下來吃一下東西(菠蘿片)再寫、再吃。這兩個動作在一段時間內都在發生著,這可以理解為并發。
另一方面我在寫這個博客的同時我在聽音樂。那么同時存在的兩個動作(寫博客、聽音樂)是同時在發生的這就是所謂的并行。
從上面兩個概念明顯可以感受到并發是包含并行操作。所以我們通常說的并發編程對于cpu來說有可能是并發的在執行也有可能是交替的在執行。
說到這里你可能會問為什么我們需要并發編程?
在求解單個問題的時候凡是涉及多個執行流程的編程模式都叫并發編程。
硬件的發展推動軟件的進度,多核時代的到來
應用系統對性能和吞吐量的苛刻要求
大數據時代的到來
移動互聯網、云計算對計算體系的沖擊
0x03 并發編程方式Java:多進程/多線程的并發實現方式
Go:協程--用戶態實現的多線程方式(goroutine)
Java并發模型在介紹java并發模型前我們來介紹下系統對多線程的實現方式。系統支持用戶態線程和內核態兩種線程的實現方式,內核態線程是cpu去調度的最小單位,所以這牽涉到用戶態線程和內核態線程之間的映射關系,用戶態線程:內核態線程 = 1:1 、 N:1 、 M:N。
1:1 這種映射關系充分利用多核的優勢,但是這種方式在用戶態進行線程切換的過程中都會涉及到內核態線程之間的切換,切換開銷大。(主要涉及內核線程運行時上下文的保存與恢復)
N:1 沒法充分利用多核的優勢,但是這種由于是用戶態的內存切換不涉及內核態線程之間的切換所以這種映射關系在線程之間切換代價小。
M:N 這種是上面兩種映射關系的結合體,集合了上面兩種映射關系的優勢,但是這也增加了線程之間這種映射關系的調度復雜度。
Java的并發編程模式是通過1:1這種映射關系來實現線程之間的并發調度。
Go并發模型Go的并發模式是通過M:N這種方式來實現并發調度的。
Go調度器中有三種重要結構:M(posix thread)、P(調度上下文,一般數量設置為和機器內核數相同,這樣能充分發揮機器的并發性能)、G(goroutine)。
一個調度上下文可以包含多個Goroutine,多個上下文所以可以所有的Goroutine都能并發的運行在CPU的多核上面。
如果有Goroutine發現找不到調度上下文,就會被放到global runqueue中,等清閑的調度上下文來撈取它進行調度。
如果調度上下文上面掛載的所有Goroutine都已經執行完畢,此時他會去global runqueue中獲取Goroutine,如果發現此時沒有獲取到,則會去別的調度上文中搶Goroutine,一般一次搶都是搶此時被搶調度上下文的一半Goroutine,確保充分利用M去被多核調度。
在享受并發編程帶來的高性能、高吞吐量的同時,也會因為并發編程帶來一些意想不到弊端。
資源的消耗,要管理這么多用戶線程、內核線程、用戶線程內核線程之間的切換調度,上下文等等這些都是由于引用了并發編程所帶來的額外消耗。
并發過程中多線程之間的切換調度,上下文的保存恢復等都會帶來額外的線程切換開銷。
編碼、測試的復雜性。和我們生活中的例子很相像,三五個人一起出去活動很容易把控,如果帶著幾十、上百人的團隊出去活動這些都會帶來額外的管理上的開銷。
真的是有陽關的地方就有黑暗啊!
上面這些都是我們沒法避免的一些問題,要引用并發編程必然會要付出點額外的代價才行。但是并發編程還帶來了一個不能忽視的問題,線程之間對同一資源的競爭訪問,造成內存對象狀態和自己的想象千差萬別。
java線程對內存的理解分為兩部分:線程工作內存(每個線程獨有的)、共享內存也叫主內存(所有的線程所共有的),下面是java線程對內存中Count對象的一次修改操作。
從主線程中讀取Count對象放入線程工作內存,后面的讀取修改都在線程工作內存中,最后(更新到主內存的時間不是確定的,可能會插入別的操作在store、write之間)更新到主內存中。所有的上述操作都是順序執行的,但是不保證連續執行。
volatile變量、synchronized塊執行結束后能保證每次去更新的值都會立即寫入到主內存中。
volatile變量很多人會認為這樣就是線程安全的,但是通過上面我們可以看到如果兩個線程同時去讀了一個volatile變量,最后一前一后更新到主內存中,這樣也會出現寫丟失的情況,所以volatile不能保證線程安全。
1) 定義線程池
private static final ExecutorService executor = Executors.newFixedThreadPool(20);
2)定義并發服務
CompletionServicecompletionService = new ExecutorCompletionService (executor);
3)提交并發任務
completionService.submit(new Callable() { @Override public void call() throws Exception { return ; } });
4)等待并發結果
for (int i = 0; i < taskSize; ++i) { Futurefuture = completionService.poll(TIME_OUT, TimeUnit.SECONDS); Result result = future.get(); }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/70030.html
摘要:所有示例代碼請見下載于基本概念并發同時擁有兩個或者多個線程,如果程序在單核處理器上運行多個線程將交替地換入或者換出內存這些線程是同時存在的,每個線程都處于執行過程中的某個狀態,如果運行在多核處理器上此時,程序中的每個線程都 所有示例代碼,請見/下載于 https://github.com/Wasabi1234... showImg(https://upload-images.jians...
摘要:比如需要用多線程或分布式集群統計一堆用戶的相關統計值,由于用戶的統計值是共享數據,因此需要保證線程安全。如果類是無狀態的,那它永遠是線程安全的。參考探索并發編程二寫線程安全的代碼 線程安全類 保證類線程安全的措施: 不共享線程間的變量; 設置屬性變量為不可變變量; 每個共享的可變變量都使用一個確定的鎖保護; 保證線程安全的思路: 1. 通過架構設計 通過上層的架構設計和業務分析來避...
摘要:精讀前端可以從多個角度理解,比如規范框架語言社區場景以及整條研發鏈路。同是前端未來展望,不同的文章側重的格局不同,兩個標題相同的文章內容可能大相徑庭。作為使用者,現在和未來的主流可能都是微軟系,畢竟微軟在操作系統方面人才儲備和經驗積累很多。 1. 引言 前端展望的文章越來越不好寫了,隨著前端發展的深入,需要擁有非常寬廣的視野與格局才能看清前端的未來。 筆者根據自身經驗,結合下面幾篇文章...
摘要:函數式編程與面向對象編程編程的本質之劍目錄編程的本質讀到兩篇文章寫的不錯綜合摘錄一下復合是編程的本質函數式程序員在洞察問題方面會遵循一個奇特的路線。在面向對象編程中,類或接口的聲明就是表面。 函數式編程與面向對象編程[5]:編程的本質 之劍 2016.5.6 01:26:31 編程的本質 讀到兩篇文章,寫的不錯, 綜合摘錄一下 復合是編程的本質 函數式程序員在洞察問題方面會遵循...
閱讀 2337·2021-11-16 11:52
閱讀 2323·2021-11-11 16:55
閱讀 750·2021-09-02 15:41
閱讀 2981·2019-08-30 15:54
閱讀 3142·2019-08-30 15:54
閱讀 2251·2019-08-29 15:39
閱讀 1507·2019-08-29 15:18
閱讀 968·2019-08-29 13:00