Vamos supor que eu tenha uma corrotina em execução em uma cadeia. Gostaria que a conclusão fosse enviada para o executor pai da cadeia (ou seja, a thread_pool
), mas também deixando a cadeia. Qual é a melhor maneira de obter esse resultado sem incorrer em sobrecarga indevida?
Aqui está um exemplo de código que ilustra o problema. Parece-me que isso f2
gera sobrecarga e quebra a localidade do raciocínio do código. Mas f3
não consigo fazer o que quero no caso específico em que o executor vinculado é o pai da cadeia. Vincular f3
a qualquer outro executor não relacionado teria sido suficiente, mas não ao pai da cadeia.
boost::asio::awaitable<void> f2(auto& pool, auto& strand)
{
assert(strand.running_in_this_thread());
co_await boost::asio::post(bind_executor(pool, boost::asio::deferred));
}
boost::asio::awaitable<void> f3([[maybe_unused]] auto& pool, auto& strand)
{
assert(strand.running_in_this_thread());
co_return;
}
int main(int argc, char* argv[])
{
boost::asio::thread_pool pool{std::thread::hardware_concurrency()};
auto strand = boost::asio::make_strand(pool);
co_spawn(strand, f2(pool, strand), bind_executor(pool, [&](std::exception_ptr exception) {
assert(!strand.running_in_this_thread()); // Succeeds, but required an explicit `post` at the end of `f2`
}));
co_spawn(strand, f3(pool, strand), bind_executor(pool, [&](std::exception_ptr exception) {
assert(strand.running_in_this_thread()); // actual result : succeeds
assert(!strand.running_in_this_thread()); // expected result, but currently fails
}));
pool.join();
}
Editar
@sehe Acho que não entendi. Primeiro, no seu código de exemplo, f1
e f3
são idênticos. Não tenho certeza se são para ser idênticos ou não. Segundo, a conclusão passada para co_spawn
está sempre vinculada a uma string, mas nunca ao executor pai da string (o pool).
Tentarei expressar meu problema de uma maneira diferente. Posso simplificar o código de exemplo removendo completamente as corrotinas. Despachar para pool
de dentro strand
executa em linha porque é verdade que já estamos dentro strand
e dentro de pool
. Se eu quiser liberar strand, posso parar de usar dispatch
e usar post
or defer
.
Voltando às corrotinas, meu entendimento atual é que a awaitable
continuação será despachada para o executor associado. Ou seja, ela será executada em linha se o meu executor associado for o pai do comando, strand
porque eu já estou executando dentro dele.
No meu código de exemplo inicial, f2
ele sai da cadeia adicionando um extra bem post
no final. Depois, a conclusão da corrotina é despachada para a mesma cadeia pool
e nunca retorna em strand
. Mas me parece que há uma sobrecarga indevida em ter ambos post
e dispatch
para a mesma continuação.
https://gcc.godbolt.org/z/c98s8EMs9
int main()
{
boost::asio::thread_pool pool{1};
auto strand = boost::asio::make_strand(pool);
// Similar to my initial `f3`
boost::asio::dispatch(bind_executor(strand, [&]() {
boost::asio::dispatch(bind_executor(pool, [&]() {
assert(strand.running_in_this_thread());
}));
}));
// Similar to my initial `f2`
boost::asio::dispatch(bind_executor(strand, [&]() {
boost::asio::post(bind_executor(pool, [&]() {
boost::asio::dispatch(bind_executor(pool, [&]() {
assert(!strand.running_in_this_thread());
}));
}));
}));
// What I'm hoping for is the following
boost::asio::dispatch(bind_executor(strand, [&]() {
boost::asio::post(bind_executor(pool, [&]() {
assert(!strand.running_in_this_thread());
}));
}));
pool.join();
}
Qual recurso você está protegendo com o fio?
Pergunto isso porque uma corrotina é sempre sua própria cadeia implícita (é um fluxo de controle puramente sequencial), então, a menos que você esteja compartilhando recursos entre cópias simultâneas da corrotina, uma cadeia é redundante aqui.
No caso geral, o Asio comunica para qual executor despachar um manipulador (de conclusão) por meio do executor associado. Você já utiliza esse mecanismo de qualquer forma (
bind_executor
).Então, no seu caso específico, isso deve ser suficiente:
ADVERTÊNCIAS
Atenção, as vertentes não são determinísticas nem exclusivas . Veja o contexto aqui: Boost Asio: Como executar um único manipulador em múltiplas vertentes independentes?
Uma amostra ao vivo com ilustração quase exaustiva das possibilidades:
Ao vivo no Coliru
Com saída típica (nesta
test()
configuração estritamente isolante, a saída é determinística, é claro):