выкинул потоки намертво, взял libcoro от M.A.Lehmann. поскольку оно только и умеет, что переключать сопрограммы по команде, захуячил простой шедулер.
ми-ми-ми, ня-ня-ня! потребление памяти упало с ~70 метров после загрузки «толстой» страницы до 2.5 метров после загрузки той же страницы. скорость по сравнению с lthread — бешеная (судя по всему, примерно такая же, как и у варианта на керналевых потоках, точно не проверял). только dns lookup блокирует всех намертво, пока не закончится (ну лень мне какой-нибудь async dns монтировать было; к тому же у меня стоит dnsmasq, который усердно кэширует запросы).
и это с учётом того, что у меня пишется на диск дохера логов. lthread понтовалась «асинхронной записью на диск», этот вариант не ебёт себе мозг и тупо делает write().
натурально, скорость можно ещё чуть-чуть увеличить, если избавиться от постоянного пидарасинья больших массивов (ленивое я, ленивое, сделало как проще было) и захерачить логи таки отдельным керналевым потоком с толстым буфером. но и так уже похоже на правду.
сама сопрограмма проксирования долна использовать обёртки net_revc() и net_send(). а если надо и то, и другое — то coro_fd_add(), очень похожее на FD_SET(), и потом coro_yield(); после возврата fd будет или с флажком read/write/both, или с флажком timeout.
натурально, accept() и connect() — тоже неблокирующие.
ах, да: i/o буфер — 8 килобайт на сопрограмму. тем не менее пердолит весьма неплохо. никаких трюков для zero copy не делается.