在本文中,咱們將討論爲創建高度併發的應用程序而逐漸創建的一些設計原理和模式。nginx
可是,值得注意的是,設計併發應用程序是一個普遍而複雜的主題,所以,沒有任何教程能夠聲稱對它的處理是詳盡無遺的。咱們這裏將介紹一些經常使用的技巧!算法
在繼續進行以前,先來了解基礎知識。首先,咱們必須闡明咱們對所謂的併發程序的理解:若是同時進行多個計算,則咱們將程序稱爲併發程序。數據庫
如今,請注意,咱們已經提到了計算是同時發生的,也就是說,它們是同時進行的。可是,它們可能同時執行或不一樣時執行,瞭解差別是很重要的,由於同時執行的計算稱爲parallel。編程
瞭解咱們如何建立併發模塊很重要。有不少選項,但咱們將在此處重點介紹兩個流行的選擇:設計模式
若是併發模塊沒必要通訊,這是很是理想的,可是一般不是這種狀況。這產生了兩種併發編程模型:瀏覽器
自從摩爾定律在處理器的時鐘速度方面碰壁以來已經有一段時間了。取而代之的是,因爲必須增加,所以咱們開始將多個處理器打包到同一芯片上,一般稱爲多核處理器。可是,聽到具備32個以上內核的處理器並不罕見。緩存
如今,咱們知道單個內核一次只能執行一個線程或一組指令。可是,進程和線程的數量能夠分別爲數百和數千。那麼,它如何真正起做用?這是操做系統爲咱們模擬併發的地方。操做系統經過時間分片來實現這一點-這實際上意味着處理器會頻繁地,不可預測地且不肯定地在線程之間切換。安全
在很大程度上,咱們在並行編程方面的經驗涉及將本機線程與共享內存一塊兒使用。所以,咱們將專一於由此產生的一些常見問題:bash
如今,咱們瞭解了併發編程的基礎知識以及其中的常見問題,是時候瞭解一些避免這些問題的常見模式了。咱們必須重申,併發編程是一項艱鉅的任務,須要大量經驗。所以,遵循某些已創建的模式可使任務更容易。服務器
咱們將針對併發編程討論的第一個設計稱爲Actor模型。這是並行計算的數學模型,基本上將一切都視爲參與者。參與者能夠相互傳遞消息,而且響應消息能夠作出本地決策。這是由卡爾·休伊特(Carl Hewitt)首次提出的,並啓發了許多編程語言。
Scala用於併發編程的主要構造是參與者。Actor是Scala中的普通對象,咱們能夠經過實例化Actor類來建立。此外,Scala Actors庫提供了許多有用的actor操做:
class myActor extends Actor { def act() { while(true) { receive { // Perform some action } } }}複製代碼
在上面的示例中,在無限循環內對receive方法的調用將使actor掛起,直到消息到達爲止。到達後,郵件將從參與者的郵箱中刪除,並採起了必要的措施。
actor模型消除了併發編程的基本問題之一-------共享內存。參與者經過消息進行通訊,而且每一個參與者依次處理其專用郵箱中的消息。可是,咱們經過線程池執行角色,並且咱們已經看到,本地線程多是重量級的,所以數量有限。
固然,這裏還有其餘模式能夠爲咱們提供幫助,稍後將介紹這些模式!
基於事件的設計明確解決了本機線程生成和操做成本高昂的問題。基於事件的設計之一是事件循環。事件循環與事件提供程序和一組事件處理程序一塊兒使用。在這種設置中,事件循環在事件提供程序上阻塞,並在到達時將事件調度到事件處理程序。
基本上,事件循環不過是事件分配器!事件循環自己能夠僅在單個本機線程上運行。那麼,事件循環中到底發生了什麼?讓咱們來看一個很是簡單的事件循環的僞代碼做爲示例:
while(true) { events = getEvents(); for(e in events) processEvent(e);}複製代碼
基本上,咱們的事件循環所要作的就是不斷尋找事件,並在發現事件後對其進行處理。該方法確實很簡單,但能夠從事件驅動的設計中受益。
使用此設計構建併發應用程序可爲應用程序提供更多控制。並且,它消除了多線程應用程序的一些典型問題,例如死鎖。
JavaScript實現事件循環以提供異步編程。它維護一個調用堆棧以跟蹤要執行的全部功能。它還維護一個事件隊列,用於發送新功能進行處理。事件循環不斷檢查調用堆棧,並從事件隊列中添加新功能。全部異步調用都會分派到一般由瀏覽器提供的Web API。
事件循環自己能夠在單個線程上運行,可是Web API提供了單獨的線程。
在非阻塞算法中,一個線程的掛起不會致使其餘線程的掛起。咱們已經看到,咱們的應用程序中只能有數量有限的本機線程。如今,阻塞在線程上的算法明顯下降了吞吐量, 並阻止了咱們構建高度併發的應用程序。
非阻塞算法始終使用底層硬件提供的比較交換原子原語。這意味着硬件將比較存儲位置的內容與給定值,而且只有它們相同時,纔會將值更新爲新的給定值。這看起來很簡單,但實際上爲咱們提供了一個原子操做,不然將須要同步。
這意味着咱們必須編寫使用此原子操做的新數據結構和庫。這爲咱們提供了多種語言的大量免等待和免鎖實現。Java具備幾種非阻塞數據結構,例如AtomicBoolean,AtomicInteger,AtomicLong和AtomicReference。
考慮一個有多個線程試圖訪問相同代碼的應用程序:
boolean open = false;if(!open) { // Do Something open=false;}複製代碼
顯然,上面的代碼不是線程安全的,而且它在多線程環境中的行爲多是不可預測的。咱們的選擇是將這段代碼與鎖同步,或者使用原子操做:
AtomicBoolean open = new AtomicBoolean(false);if(open.compareAndSet(false, true) { // Do Something}複製代碼
如咱們所見,使用像AtomicBoolean這樣的非阻塞數據結構能夠幫助咱們編寫線程安全的代碼,而不會沉迷於鎖的弊端!
咱們已經看到,能夠經過多種方式構造併發模塊。儘管編程語言確實有所做爲,但主要是底層操做系統如何支持該概念。可是,因爲本機線程支持的基於線程的併發在可伸縮性方面遇到了新的障礙,所以咱們始終須要新的選擇。
咱們可使用的一種解決方案是綠色線程。綠色線程是由運行時庫調度的線程,而不是由底層操做系統本地調度的線程。儘管這並不能解決基於線程的併發中的全部問題,但在某些狀況下,它確定能夠爲咱們提供更好的性能。
如今,除非咱們選擇使用的編程語言支持綠色線程,不然使用綠色線程並不是易事。並不是每種編程語言都具備此內置支持。一樣,咱們能夠經過不一樣的編程語言以很是獨特的方式來實現咱們所謂的綠色線程。讓咱們來看一些可用的選項。
Go編程語言中的Goroutine 是輕量級線程。它們提供能夠與其餘功能或方法同時運行的功能或方法。Goroutines 很是便宜,由於它們從開始只佔用幾千字節的堆棧大小。
最重要的是,goroutines與較少數量的本機線程複用。此外,goroutine使用通道相互通訊,從而避免了對共享內存的訪問。咱們幾乎得到了所需的一切,而後猜想-什麼都不作!
在Erlang中,每一個執行線程稱爲一個進程。可是,這與咱們到目前爲止討論的過程不太同樣!Erlang進程重量輕,內存佔用少,而且建立和處理速度快,調度開銷低。
在幕後,Erlang進程不過是運行時爲之調度的功能。並且,Erlang進程不共享任何數據,它們經過消息傳遞相互通訊。這就是爲何咱們首先稱這些「過程」的緣由!
Java併發的故事一直在不斷髮展。Java確實從一開始就對綠色線程(至少對Solaris操做系統)提供了支持。可是,因爲障礙超出了本教程的範圍,所以已中止使用。
從那時起,Java中的併發所有與本地線程有關,以及如何巧妙地使用它們!可是出於顯而易見的緣由,咱們可能很快就會在Java中有了一個新的併發抽象,稱爲光纖。Project Loom建議將延續與纖維一塊兒引入,這可能會改變咱們用 Java 編寫併發應用程序的方式!
這只是對不一樣編程語言中可用功能的簡要介紹。其餘編程語言還嘗試了更多有趣的方式來處理併發。
此外,值得注意的是,在設計高度併發的應用程序時,上一節中討論的設計模式與編程語言對相似綠線程的抽象的支持的組合可能會很是強大。
實際應用程序一般具備多個組件,這些組件經過導線相互交互。咱們一般經過Internet對其進行訪問,它包含多種服務,例如代理服務,網關,Web服務,數據庫,目錄服務和文件系統。
在這種狀況下,咱們如何確保高併發性?讓咱們探索其中的一些層以及構建高度併發應用程序所具備的選項。
構建高併發應用程序的關鍵是使用此處討論的一些設計概念。咱們須要爲工做選擇合適的軟件-----已經結合了其中一些實踐的軟件。
Web一般是用戶請求到達的第一層,所以在此處不可避免地須要進行高併發性設置。讓咱們看看其中的一些選項:
在設計應用程序時,有多種工具可幫助咱們構建高併發性,先檢查一下其中一些可用的庫和框架:
沒有數據就沒有完整的應用程序,數據來自持久性存儲。當咱們討論有關數據庫的高併發性時,大多數重點仍放在NoSQL系列上。這主要是因爲NoSQL數據庫能夠提供線性可伸縮性,可是在關係型變量中卻很難實現。讓咱們看一下數據層的兩個流行工具:
現代世界中沒有任何旨在實現高併發性的Web應用程序可以承受每次訪問數據庫的負擔。這就讓咱們選擇了一個緩存-----最好是能夠支持咱們高度併發的應用程序的內存中緩存:
固然,在咱們追求構建高度併發的應用程序時,咱們幾乎沒有涉及任何可用的內容。重要的是要注意,除可用軟件外,咱們的要求還應指導咱們建立適當的設計。這些選項中的某些選項可能適用,而其餘選項可能不合適。
並且,別忘了還有更多可用的選項可能更適合咱們的要求。
在本文中,咱們討論了併發編程的基礎。咱們瞭解了併發的一些基本方面及其可能致使的問題。此外,咱們演示了一些設計模式,這些模式能夠幫助咱們避免併發編程中的典型問題。
最後,咱們介紹了一些可用於構建高度並行的端到端應用程序的框架,庫和軟件。