併發編程的幾種形式

併發編程的幾種形式web


在併發編程中咱們常常聽到如下一些概念,今天我將嘗試進行闡述。編程

1、併發promise

同時幹多件事情,這就是併發的做用。安全

web服務器能夠利用併發同時處理大量用戶的請求。服務器

只要咱們須要程序同時幹多件事情,咱們就須要併發。數據結構

2、多線程多線程

併發編程的一種形式,其採用多個線程執行程序。閉包

線程是一個獨立的運行單元,每一個進程內部有多個線程,每一個線程能夠各自同時執行指令。併發

每一個線程有本身獨立的棧,可是與進程內的其餘線程共享內存。框架

線程池是線程更普遍的一種應用形式,其維護着必定數量的工做線程,這些線程等待着執行分配下來的任務。線程池能夠隨時監測線程的數量

線程池催生了另一種重要的併發形式:並行處理。

多線程並非併發編程的惟一形式,雖然.NETJava等語言框架都對底層線程類型提供了支持,可是對開發人員並不友好,最新的.NETJava

都提供了更高級別的抽象,讓咱們開發併發程序更加方便高效。

3、並行處理

將大塊的任務分割成相互獨立的小塊,並分配給多個同時運行的線程處理。

並行處理採用多線程,提升了處理器的利用效率。

並行編程一般不適合服務器系統,服務器自己都具備併發處理能力。

數據並行能夠處理大量的彼此獨立的數據,好比Hadoop等大數據處理框架。

任務並行能夠將彼此獨立的拆分任務同時執行。

下邊看下.NET中提供的並行編程

使用Parallel.ForEach進行數據並行

void RotateMatrices(IEnumerable<Matrix> matrices, float degrees)
{
    Parallel.ForEach(matrices, matrix => matrix.Rotate(degrees));
}

 

使用Parallel.ForEach進行數據並行

IEnumerable<bool> PrimalityTest(IEnumerable<int> values)
{
    return values.AsParallel().Select(val => IsPrime(val));
}

 

數據的獨立性是並行性最大化的前提,否爲了確保安全性就須要引入同步,從而影響程序的並行程度。

只能最大程度的並行,可是老是消滅不了同步,數據並行的結果老是須要進行聚合,Parallel實現了響應的重載及map/reduce函數。

Parallel類的Invoke方式能夠實現任務並行

void ProcessArray(double[] array)
{
    Parallel.Invoke(
        () => ProcessPartialArray(array, 0, array.Length / 2),
        () => ProcessPartialArray(array, array.Length / 2, array.Length)
    );
}
void ProcessPartialArray(double[] array, int begin, int end)
{
    // CPU 密集型的操做......
}        

 


任務並行也依賴任務的獨立性,同時要注意閉包對變量的引用,即便是值類型也是引用。

任務不要特別短,也不要特別長。若是任務過短,把數據分割進任務和在線程池中調度任務的開銷會很大。若是任務太長,線程池就不能進行

有效的動態調整以達到工做量的平衡。


4、異步編程

併發編程的一種形式,它採用future模式或者回調(callback)機制,以免產生沒必要要的線程。

回調和事件做爲老式的異步編程,在服務器端和GUI中都有普遍的應用。

一個future或者promise表明一些即將完成的操做,在.NET中的TPL中有TaskTask<TResult>,在Java中有FutureTask,在JS中有fetch(新版Firefox

Chorm支持)

異步編程能夠在啓動一個操做以後,能夠繼續執行而不會被阻塞,待操做執行完以後,通知future或者執行回調函數,以便告知操做結束。

異步編程是一種功能強大的併發形式,但傳統的異步編程特別複雜並且不易於代碼維護。.NETNode.JS支持的asyncawait,讓異步編程變得

跟串行編程同樣簡單。


下面看下.NET 的兩個關鍵字: async await async 關鍵字加在方法聲明上,它的主要目的是使方法內的 await 關鍵字生效。若是 async 方法有

返回值,應返回 Task<T> ;若是沒有返回值,應返回 Task 。這些task 類型至關於 future,用來在異步方法結束時通知主程序。下邊的例子同時請求兩

個服務地址,只要有一個返回結果便可完成。


// 返回第一個響應的 URL 的數據長度。
private static async Task<int> FirstRespondingUrlAsync(string urlA, string urlB)
{
    var httpClient = new HttpClient();
    // 併發地開始兩個下載任務。
    Task<byte[]> downloadTaskA = httpClient.GetByteArrayAsync(urlA);
    Task<byte[]> downloadTaskB = httpClient.GetByteArrayAsync(urlB);
    // 等待任意一個任務完成。
    Task<byte[]> completedTask =
    await Task.WhenAny(downloadTaskA, downloadTaskB);
    // 返回從 URL 獲得的數據的長度。
    byte[] data = await completedTask;
    return data.Length;
}

 


5、響應式編程

一種聲明式的編程模式,程序在該模式中對事件進行響應。

程序針對不一樣的事件進行響應並更新本身的狀態。

異步編程針對啓動的操做,響應編程針對能夠任何事件重複發生的異步事件。

響應式編程基於「可觀察的流」(observable stream)。一旦申請了可觀察流,就能夠收到任意數量的數據項( OnNext ),而且流在結束時會發出一個錯誤(

OnError )或一個結束的通知( OnCompleted )。實際的接口以下

interface IObserver<in T>
{
    void OnNext(T item);
    void OnCompleted();
    void OnError(Exception error);
}

interface IObservable<out T>
{
    IDisposable Subscribe(IObserver<T> observer);
}

 

微軟的 Reactive Extensions(Rx)庫已經實現了全部接口。下面的代碼中,前面是咱們不熟悉的操做符( Interval Timestamp ),最後是一個 Subscribe ,

可是中間部分是咱們在 LINQ 中熟悉的操做符: Where Select LINQ 具備的特性,Rx也都有。Rx 在此基礎上增長了不少它本身的操做符,特別

是與時間有關的操做符:

Observable.Interval(TimeSpan.FromSeconds(1))
.Timestamp()
.Where(x => x.Value % 2 == 0)
.Select(x => x.Timestamp)
.Subscribe(x => Trace.WriteLine(x));

 

上面的代碼中,首先是一個延時一段時間的計數器( Interval ),隨後、後爲每一個事件加了一個時間戳( Timestamp )。接着對事件進行過濾,只包含偶數

( Where ),選擇了時間戳的值( Timestamp ),而後當每一個時間戳值到達時,把它輸入調試器( Subscribe )。可觀察流的定義和其訂閱是互相獨立的。

上面最後一個例子與下面的代碼等效:

IObservable<DateTimeOffset> timestamps =
Observable.Interval(TimeSpan.FromSeconds(1))
.Timestamp()
.Where(x => x.Value % 2 == 0)
.Select(x => x.Timestamp);
timestamps.Subscribe(x => Trace.WriteLine(x));

 

一種常規的作法是把可觀察流定義爲一種類型,而後將其做爲 IObservable<T> 資源使用。其餘類型能夠訂閱這些流,或者把這些流與其餘操做符

組合,建立另外一個可觀察流Rx 的訂閱也是一個資源。 Subscribe 操做符返回一個 IDisposable ,即表示訂閱完成。當你響應了那個可觀察流,就得處

理這個訂閱。對於hot observable(熱可觀察流)cold observable(冷可觀察流)這兩種對象,訂閱的作法各有不一樣。一個 hot observable 對象是指一直

在發生的事件流,若是在事件到達時沒有訂閱者,事件就丟失了。例如,鼠標的移動就是一個 hot observable 對象。cold observable 對象是始終沒有

輸入事件(不會主動產生事件)的觀察流,它只會經過啓動一個事件隊列來響應訂閱。例如,HTTP 下載是一個 cold observable 對象,只有在訂閱後

纔會發出 HTTP 請求。

6、併發集合和不可變集合

大多數併發集合經過快照,既能夠確保一個線程修改數據,同時也能夠容許多個線程同時枚舉數據。

不可變集合的沒法修改性確保了全部操做的簡潔性,特別適合在併發編程中使用。

7、併發編程與函數編程

大多數併發編程技術本質上都是函數式(functional) 的。

函數式編程理念簡化併發編程的設計。每個並行的片斷都有輸入和輸出。他們不依賴於全局(或共享)變量,也不會修改全局(或共享)數據結構。

函數式編程的數據不變性在確保併發安全性的前提下,同時也防止了併發的活躍性問題。

相關文章
相關標籤/搜索