併發每每和並行一塊兒被說起,可是咱們應該明確的是「併發」不等同於「並行」html
• 併發 :同一時間 對待 多件事情 (邏輯層面)java
• 並行 :同一時間 作(執行) 多件事情 (物理層面)golang
併發能夠構造出一種問題解決方法,該方法可以被用於並行化,從而讓本來只能串行處理的事務並行化,更好地發揮出當前多核CPU,分佈式集羣的能力。web
可是,併發編程和人們正常的思惟方式是不同的,所以纔有了各類編程模型的抽象來幫助咱們更方便,更不容易出錯的方式構建併發程序。下面將簡單介紹一些常見的併發編程模型,但願能幫助你們對併發編程有更多的興趣。這些模型都有各自的優點,須要根據應用場景挑選,而挑選的前提是可以深刻地理解它們。編程
多線程模型是用於處理併發的最通用手段,在 C/C++/JAVA 等語言中普遍存在。主要特性有:設計模式
l 多個相互獨立的執行流.多線程
l 共享內存(狀態).併發
l 搶佔式的調度.框架
l 依賴鎖,信號量等同步機制異步
多線程程序容易編寫(由於寫的是順序程序),可是難分析,難調試,更容易出錯,常見的有競爭條件,死鎖,活鎖,資源耗盡,優先級反轉… 等等。
爲了下降多線程模型編寫難度,不少語言都一直在不斷地引入併發編程方面新的特性,例如Java。從最先1996年的JDK1.0 版本起就已經有了Thread,Runnable類,確立了最基礎的線程模型,這已經比直接調用POSIX接口構建多線程應用的方式有了很大的提升。而後在JDK5時引入了java.util.concurrent包,其中的線程池(Thread Pool,Executors)等類庫,使得Java併發編程的易用性有了更好的提高。
到了JDK7, Fork/Join框架被引入,雖然底層同樣是基於ExecutorService線程池的實現。但在編寫併發邏輯時會比傳統多線程方式更加直觀,開發者能夠將一個大的做業抽象爲幾個能夠併發的子任務的結果整合;而每一個子任務又能夠繼續按此邏輯繼續劃分,充分發揮現代多核CPU的性能。
同時,Fork/Join框架中還內置了Work-Stealing的任務調度機制,可以在儘可能下降線程競爭的同時嘗試自動均衡各工做線程之間的任務負載。以下圖所示:
Ø 4個線程每一個都有獨立的工做隊列,避免單任務隊列競爭
Ø 隊列中的任務採用相似LIFO方式進出。因爲總體做業都是按照一個大任務fork出多個子任務來抽象,所以能夠視爲越大粒度的任務會沉在隊列的越底部。
Ø 當某個線程(示例中爲線程D)的工做隊列爲空時,該線程就會自動嘗試從另外一個線程(示例中爲線程A)的隊列底部」偷「一個任務過來執行。因爲是從底部竊取的任務,能夠假設這個任務將展開更多的子任務,從而減小竊取動做的產生,下降線程爭用頻率。
經過這些手段,Fork/Join框架能幫助開發者無需在考慮手動實現併發任務執行時的高效同步邏輯。
隨後,JDK8中又引入了並行流(Parallel Streams)的概念, 該特性基於Fork/Join框架,但在易用性方面繼續有所提高。並行流採用共享線程池的思路,從而連線程/線程池的配置邏輯都幫開發者簡化了。固然,正是由於這個共享池( ForkJoinPool.commonPool() )是被JVM管理,同時被JVM內的全部線程共享,也致使了一些隱患,若是開發者並無瞭解並行流的底層實現機制,則可能致使應用中利用到並行流的任務產生停滯現象。例以下面的代碼示例:
因爲 WS.url(url).get()會觸發HTTP請求,所以執行到這一句代碼時,線程池會被阻塞在IO操做上,結果致使了當前JVM中全部並行流中的任務所有被阻塞。
「回調」是一個很容易理解的名詞。簡單來講:某個函數(A)能夠接受另外一個函數(B)做爲參數,在執行流程到某個點時做爲參數的函數B就會被函數A調用執行,這個行爲就被稱爲回調。
現實中,回調經常用於異步事件。即,函數A通常會在函數B沒有被調用的狀況下就先返回,而在某個異步事件發生時再觸發調用函數B。
可是濫用回調嵌套,就會致使著名的」callback hell」問題,代碼難以閱讀和維護。例以下面的片斷:
爲了不此類大坑,咱們能夠參考如下幾類解決方案:
l Promises/A+規範: 它是一種用於管理異步回調的代碼結構和流程,一種回調的語法糖。能夠把本來嵌套的回調函數展平,使得代碼邏輯更清楚。例如片斷:
l Generator: 生成器/半協程方式: 能夠將一個函數執行暫停,並保存上下文, 將控制權交還給調用者;當再次被調用時,可以恢復當時的暫停狀態繼續執行。因此generator函數的行爲表現和迭代器很相似,每次觸發它的時候能夠獲取到新的結果,而不是像傳統函數所有執行結束後一口氣返回一系列值。 代碼片斷:
l Async/Await: 能夠視爲Generator方式的語法糖,可以更好地展現異步調用的語義: async關鍵字用於表示該函數中有異步操做;await關鍵字表示須要等待(異步方式)後繼表達式的結果。
Actor模型首先是由Carl Hewitt在1973年提出定義, 隨後由Erlang OTP (Open Telecom Platform) 推廣開來。Actor屬於併發組件模型, 經過組件方式定義併發編程範式的高級階段,避免使用者直接接觸多線程併發或線程池等基礎概念,其消息傳遞更加符合面向對象的原始意圖。
傳統多數流行的語言併發是基於多線程之間的共享內存,使用同步機制來防止寫爭奪。而Actors使用消息模型,每一個Actors在同一時間處理最多一個消息,能夠發送消息給其餘Actors,保證了單獨寫原則,從而巧妙避免了多線程的寫爭奪。
Actor模型不只僅對於單機的併發應用開發有意義,對於分佈式應用的開發也是一個能夠大展手腳的場景: 節點之間互相獨立,只能靠消息通信,異步消息避免節點瓶頸等特性都很是貼合Actor模型的使用。
Actor模型的特色是:
l 萬物皆是Actor
l Actor之間徹底獨立,只容許消息傳遞,不容許其餘」任何」共享
l 每一個Actor最多同時只能進行同樣工做
l 每一個Actor都有一個專屬的命名Mailbox(非匿名)
l 消息的傳遞是徹底異步的;
l 消息是不可變的
在Java中,能夠利用Akka進行Actor編程模型的應用開發。Akka 將自身定義爲一套用於構建JVM上高併發,容錯式,分佈式,消息驅動特性應用開發的工具包和運行環境。詳細介紹可參見官網: http://akka.io/。
下面用代碼片斷來展現下基於AKKA開發示例:
咱們定義了兩個Actor: HelloWorld 和 Greeter.
l HelloWorld會處理幾個消息
n 啓動消息(能夠將preStart方法的調用視爲收到一個專屬啓動事件的處理): 主動向Greeter(ActorRef能夠視爲對應Actor的專屬Mailbox)發送一個Msg.GREET消息
n Msg.Done消息: 接收完該消息後,中止當前Actor
n 其餘消息: 調用unhandled() 處理
l Greeter會處理這些消息:
n Msg.GREET消息: 向System.out輸出字符串, 並向消息的發送者回復一個Msg.Done消息
n 其餘消息: 調用unhandled() 處理
HelloWorld,Greeter能夠根據須要實例化在多個線程中執行,編碼過程當中不須要考慮傳統多線程中的Lock/Wait/Notify等同步手段就能讓這兩個Actor之間分別指示對方完成相應動做。
CSP(Communicating Sequential Processes)是由Tony Hoare在1978的論文上首次提出的。 它是處理併發編程的一種設計模式或者模型,指導併發程序的設計,提供了一種併發程序可實踐的組織方法或者設計範式。經過此方法,能夠減小併發程序引入的其它缺點,減小和規避併發程序的常見缺點和bug,而且能夠被數學理論所論證。
CSP將程序分紅兩種模塊,Processor 與 Channel:Processor 表明了執行任務的順序單元,它們內部沒有併發,而Channel表明了併發流之間的信息交互,如共享數據的交換、修改、消息傳遞等等。
除了Channel,Processor之間再無聯繫,這樣就將併發同步做用縮小在Channel之處,使得問題獲得了約束、集中。同步操做與爭用並無消失,只是聚焦在Channel之上。Processor之間的協做,Channel提供原語來支持,如Barrier等。
CSP 的好處是使得系統較爲清晰,Processor 之間是解耦合的,職責也很是清楚,容易理解和維護。
l 工做者之間不直接進行通訊
l 工做者向不一樣的通道中發佈本身的消息(事件)。其餘工做者們能夠在這些通道上監聽消息,發送者不知道具體誰在執行(匿名)
l 消息交互是同步方式
在Java中對於CSP模型的實現庫有JCSP。 同時在JDK中的SynchronousQueue,和CSP中的Channel有殊途同歸之妙。Executors.newCachedThreadPool()中就利用到了SynchronousQueue,任務提交者是並不清楚底層哪一個線程會處理提交的任務,而且當提交任務操做完成時必然已經有某個線程接受了該任務(並不表明線程開始執行),所以提交操做此次消息交互是同步的方式。這和Executors.newFixedThreadPool()之類建立的線程池是大相徑庭的,其餘線程池在提交操做完成時,任務分配給線程這個動做是異步的。
此外,Go語言內置的goroutines & channels併發模型就是參考了CSP的思想,所以Go的併發編程強調不要利用共享內存來進行線程通信,而應該依靠通信來共享數據(Do not communicate by sharing memory; instead, share memory by communicating),儘可能避免鎖和線程爭用。
l http://web.stanford.edu/~ouster/cgi-bin/papers/threads.pdf
l https://en.wikipedia.org/wiki/Actor_model
l https://en.wikipedia.org/wiki/Communicating_sequential_processes
l https://talks.golang.org/2012/waza.slide#1
l https://www.quora.com/What-are-the-differences-between-parallel-concurrent-and-asynchronous-programming
l http://wiki.commonjs.org/wiki/Promises/A
l http://www.ibm.com/developerworks/cn/java/j-csp1.html
l http://blog.takipi.com/forkjoin-framework-vs-parallel-streams-vs-executorservice-the-ultimate-benchmark/
l https://www.cs.kent.ac.uk/projects/ofa/jcsp/cpa2007-jcsp.pdf
l http://tutorials.jenkov.com/java-concurrency/index.html
網易雲新用戶大禮包:https://www.163yun.com/gift
本文來自網易雲社區,經做者邱晟受權發佈。