2017-04-19 70 views
8

什麼是的協同程序?什麼是C++ 20中的協程?

它與「Parallelism2」或/和「Concurrency2」有什麼不同?(看下圖)?

下圖來自ISOCPP。

https://isocpp.org/files/img/wg21-timeline-2017-03.png

enter image description here

+3

回答「*協程*的概念與*並行*和*併發*的區別是什麼? - https://en.wikipedia.org/wiki/Coroutine –

+0

相關:http://stackoverflow.com/q/35121078/103167 –

+3

一個很好的和易於遵循介紹到協程是詹姆斯McNellis的演講「簡介到C++協程「(Cppcon2016) – philsumuru

回答

20

在一個抽象的層次上,Coroutines將執行狀態與擁有執行線程的想法分離開來。 SIMD(單指令多數據)具有多個「執行線程」,但只有一個執行狀態(它只適用於多個數據)。可以說並行算法有點像這樣,因爲你有一個「程序」運行在不同的數據上。

線程有多個「執行線程」和多個執行狀態。你有多個程序和多個執行線程。

協程有多個執行狀態,但沒有一個執行線程。你有一個程序,程序有狀態,但它沒有執行的線程。


協程的最簡單的例子是來自其他語言的生成器或枚舉。

在僞碼:

function Generator() { 
    for (i = 0 to 100) 
    produce i 
} 

Generator被調用,並在第一時間它被稱爲它返回0。它的狀態被記住(多少狀態隨着協程的實現而變化),並且當你下一次調用它時它會繼續停止。所以它下一次返回1。然後2.

最後它到達循環的結尾並從函數的結尾落下;協程完成。(這裏發生的事情根據我們所談論的語言而有所不同;在python中,它會引發異常)。

協程將此功能引入C++。

協程有兩種;堆疊和堆疊。

無堆棧協程只將局部變量存儲在其狀態及其執行位置。

堆棧協程存儲整個堆棧(如線程)。

無堆棧協程可以是非常輕的重量。我讀到的最後一個提案基本上將你的函數改寫成一個有點像lambda的東西;所有局部變量進入對象的狀態,標籤用於跳轉到協程「產生」中間結果的位置。

產生一個值的過程稱爲「yield」,因爲協程有點像合作多線程;你正在讓執行點返回給調用者。

Boost有一個堆棧協程的實現;它可以讓你調用一個函數來讓你放棄。堆棧協程更強大,但也更昂貴。


協程比簡單的發生器更多。您可以等待協程中的協程,它可以讓您以有用的方式組合協程。

協程,就像if,循環和函數調用一樣,是另一種「結構化goto」,可讓您以更自然的方式表達某些有用的模式(如狀態機)。


在C++中的Coroutines的具體實現有點有趣。

在最基本的級別上,它向C++添加了幾個關鍵字:co_returnco_awaitco_yield,以及一些與它們一起工作的庫類型。

一個函數通過在其主體中具有一個函數而成爲協程。所以從他們的聲明中,他們與功能是無法區分的。

當在函數體中使用這三個關鍵字之一時,會發生一些標準的強制檢查返回類型和參數,並將函數轉換爲協程。這個檢查告訴編譯器在函數暫停時存儲函數狀態的位置。

最簡單的協程是發電機:

generator<int> get_integers(int start=0, int step=1) { 
    for (int current=start; current+= step) 
    co_yield current; 
} 

co_yield暫停功能的執行,存儲該狀態在generator<int>,然後返回通過該generator<int>current值。

您可以遍歷返回的整數。

co_await同時讓您將一個協程接合到另一個協程上。如果你在一個協程中,並且在進行之前你需要一個等待的東西的結果(通常是一個協程),那麼你需要co_await就可以了。如果他們準備好了,你立即着手;如果不是,你暫停直到你等待的等待準備就緒。

std::future<std::expected<std::string>> load_data(std::string resource) 
{ 
    auto handle = co_await open_resouce(resource); 
    while(auto line = co_await read_line(handle)) { 
    if (std::optional<std::string> r = parse_data_from_line(line)) 
     co_return *r; 
    } 
    co_return std::unexpected(resource_lacks_data(resource)); 
} 

load_data是指定的資源被打開,我們成功地解析的地方,我們發現所請求的數據點時產生一個std::future協同程序。

open_resourceread_line s可能是異步協程,可以打開文件並從中讀取行。 co_awaitload_data的掛起和就緒狀態連接到其進度。

C++協程比這更加靈活,因爲它們在那裏作爲用戶空間類型的頂部的一組語言特性的最小實現。用戶空間類型有效地界定什麼co_returnco_awaitco_yield意味着 - 我看到人們用它來實現這樣的單子可選表達式,在一個空的可選一個co_await自動propogates空狀態到外可選:

std::optional<int> add(std::optional<int> a, std::optional<int> b) { 
    return (co_await a) + (co_await b); 
} 

,而不是

std::optional<int> add(std::optional<int> a, std::optional<int> b) { 
    if (!a) return std::nullopt; 
    if (!b) return std::nullopt; 
    return *a + *b; 
} 
+0

這是一個什麼樣的協同程序是最清晰的解釋之一這是我讀過的,將它們與SIMD和古典主題進行比較和區分是一個很好的想法。 – Omnifarious

1

協程應該是(在C++),其能夠「等待」一些其他常規完成,並提供任何需要用於功能暫停,暫停,等待,例行繼續。 C++人最感興趣的特性是,協程在理想情況下不會有堆棧空間...... C#已經可以用await和yield來做類似的事情,但C++可能需要重建才能進入。

併發性是重點關注分離關注點,而關注點是該計劃應該完成的任務。這種關注的分離可以通過許多手段來實現......通常是某種形式的授權。併發的思想是,許多進程可以獨立運行(關注點分離),而'偵聽者'會將這些分離的關注點所產生的任何東西指向任何它應該去的地方。這嚴重依賴於某種異步管理。有許多併發的方法,包括面向方面的編程和其他方法。 C#有'委託'操作符,工作得非常好。

並行性聽起來像併發性,可能涉及但實際上是一個物理結構,涉及許多處理器或多或少地以並行方式排列,並且軟件能夠將代碼的一部分引導到將要運行的不同處理器,結果將被同步接收。

+4

併發性和關注點分離是*完全*無關。協同程序不提供暫停日常信息,它們是* *可恢復程序。 –

2

一個協同程序就像是它有多個return語句,並要求當第二次不啓動在開始的功能,但在第一個指令執行AF C函數之前執行的回報。該執行位置與所有自動變量一起保存在非協程函數中。

從微軟先前的實驗協同程序實現並利用複製的棧,所以你甚至可以從深嵌套函數返回。但是這個版本被C++委員會拒絕了。例如,您可以使用Boosts光纖庫獲得此實現。