Introdução
Este artigo é a tradução de um capítulo do livro Concurrency with Modern C ++ de Rainer Grimm , que é uma versão mais refinada e extensa do artigo em seu site . Como a tradução inteira não se enquadra no quadro deste artigo, dependendo da reação à publicação, postarei o restante.
Corrotinas
Corrotinas são funções que podem pausar ou retomar sua execução enquanto mantêm seu estado. A evolução das funções em C ++ deu um passo à frente. Corrotinasmais provável de incluir entrou em C ++ 20.
A ideia de co-rotinas, introduzida como nova no C ++ 20, é bem antiga. O conceito de co-rotina foi proposto por Melvin Conway . Ele usou esse conceito em sua publicação de desenvolvimento do compilador de 1963. Donald Knuth referiu-se aos procedimentos como um caso especial de corrotinas. Às vezes, leva tempo para que esta ou aquela ideia seja aceita.
Com novas palavras co_await- chave e co_yieldC ++ 20, ele expande o conceito de execução de função em C ++ com dois novos conceitos.
Graças a ele co_await expression, é possível pausar e retomar a execução expression. Quando usada co_await expressionem uma função, a funcchamada auto getResult = func()não está bloqueando se o resultado da função fornecida não estiver disponível. Em vez de um bloqueio que consome recursos, uma espera amigável de recursos é executada.
co_yield expressionpermite que você implemente funções geradoras. Geradores são funções que retornam um novo valor a cada chamada subsequente. A função do gerador é semelhante a fluxos de dados dos quais os valores podem ser recuperados. Os fluxos de dados podem ser infinitos. Portanto, esses conceitos são fundamentais para a avaliação preguiçosa em C ++.
Funções geradoras
. getNumbers begin end inc. begin end, inc .
// greedyGenerator.cpp
#include <iostream>
#include <vector>
std::vector<int> getNumbers(int begin, int end, int inc = 1) {
std::vector<int> numbers; // (1)
for (int i = begin; i < end; i += inc) {
numbers.push_back(i);
}
return numbers;
}
int main() {
const auto numbers = getNumbers(-10, 11);
for (auto n : numbers) {
std::cout << n << " ";
}
std::cout << "\n";
for (auto n : getNumbers(0, 101, 5)) {
std::cout << n << " ";
}
std::cout << "\n";
}, getNumbers , std::iota C++11.
, :
$ ./greedyGenerator
-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10
0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100
. -, numbers (. (1) ) . 5 1000 . -, getNumbers .
// lazyGenerator.cpp
#include <iostream>
#include <vector>
generator<int> generatorForNumbers(int begin, int inc = 1) {
for (int i = begin; ; i += inc) { // (4)
co_yield i; // (3)
}
}
int main() {
const auto numbers = generatorForNumbers(-10); // (1)
for (int i = 1; i <= 20; ++i) { // (5)
std::cout << numbers << " ";
}
std::cout << "\n";
for (auto n : generatorForNumbers(0, 5)) { // (2)
std::cout << n << " ";
}
std::cout << "\n";
} : , .. . .
, getNumbers greedyGenerator.cpp std::vector<int>, generatorForNumbers lazyGenerator.cpp generator. numbers (1) generatorForNumbers(0, 5) (2) . Range-based for . , i co_yield i (. (3)) . , .
generatorForNumbers(0, 5) (. (2)) (just-in-place usage).
. generatorForNumbers , for (4) . , .., , (5) . , , (2) .
- . - , , , . , , . , .
C++20 , (first-class) (stackless).
. , .
. , .
. . (resumable functions).
.
:
- ( ).
- , .
- .
- c , , , , .
- .
, . , - 1MB Windows 2MB Linux.
- co_return
- co_await
- co_yield
- co_await expression range-based for
return . (auto), ().
, constexpr , , main .
proposal N4628.
co_return, co_yield co_await
co_return .
co_yield . generator<int> generatorForNumbers(int begin, int inc = 1) generator<int> promise p , co_yield i co_await p.yield_value(i).co_yield i . .
co_await , . exp co_await exp , , ( awaitables). exp , : await_ready, await_suspend await_resume.
C++20 2 awaitables: std::suspend_always std::suspend_never.
std::suspend_always
struct suspend_always {
constexpr bool await_ready() const noexcept { return false; }
constexpr void await_suspend(coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};
, awaitable std::suspend_always , await_ready false. std::suspend_never.
std::suspend_never
struct suspend_never {
constexpr bool await_ready() const noexcept { return true; }
constexpr void await_suspend(coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};
co_await .
Acceptor acceptor{443};
while (true) {
Socket socket = acceptor.accept(); // blocking
auto request = socket.read(); // blocking
auto response = handleRequest(request);
socket.write(response); // blocking
}
. 443 , , . .
co_await .
Acceptor acceptor{443};
while (true) {
Socket socket = co_await acceptor.accept();
auto request = co_await socket.read();
auto response = handleRequest(request);
co_await socket.write(response);
}
20 , . .
: promise , handle frame .
Promise .
Handle handle frame .
Frame , . promise , , (suspention point), , , .
:
- .
- frame .
workflow
co_return co_yield co_await .
{
Promise promise;
co_await promise.initial_suspend();
try {
< >
} catch (...) {
promise.unhandled_exception();
}
FinalSuspend:
co_await promise.final_suspend();
}
Workflow :
-
- frame .
- frame .
- promise
promise. -
promise.get_return_object()handle . . -
promise.initial_suspend()co_await. promisesuspend_neversuspend_always. -
co_await promise.initial_suspend()
-
-
promise.get_return_object()
-
-
co_return
-
promise.return_void()co_returnco_return expression,expressionvoid - chamado
promise.return_value(expression)paraco_return expression, ondeexpressioné do tipo que não sejavoid - apaga toda a pilha de variáveis criadas
- resultados chamados
promise.final_suspend()e esperadosco_await
-
- A co-rotina é destruída (por meio de conclusão por meio de
co_returnuma exceção não tratada ou por meio do identificador de co-rotina)
- o destruidor do objeto de promessa é chamado
- o destruidor dos parâmetros da função é chamado
- libera memória usada pelo quadro de co-rotina
- passando a execução para o chamador
Quando a co-rotina termina com uma exceção não tratada, acontece o seguinte:
- a exceção é capturada e chamada
promise.unhandled_exception()do bloco catch - chamado
promise.final_suspend()eco_awaitresultado esperado