Как стать автором
Обновить
222
59.5
Antony Polukhin @antoshkka

Эксперт-разработчик C++ в Яндекс Go

Отправить сообщение

Препроцессором такого не достичь:

int foo(int i) {
    switch (i) {
      case 42: return 64;
      case 142: return 6;
      case 1024: return 0;
    }
    // Без следующей строчки компилятор будет сыпать предупреждениями
    std::unreachable();
}

Их готовятся добавлять по кусочкам в стандарт. Например на подходе [[assume(x)]]

Будет, приняли в стандарт в прошлый раз: С++23 — feature freeze близко

Все эти вещи успели проскочить нужные подгруппы и есть все шансы увидеть их в C++23

Хранить вторым аргументом исключение - это должно быть редким кейсом. Упор в std::expected сделан на хранение кодов ошибки - вы так получите тривиальность копирования expected, меньший размер объекта, более быструю передачу через регистры и т.п.

А вот кидать код ошибки - это очень сомнительное удовольствие, лучше уж кинуть что-то отнаследованное от std::exception, а именно bad_expected_access. Ну и ловить в коде тогда можно весьма ожидаемый std::exception, а например не srd::errc.

Что же касается operator* - в дебаге там будет assert, который поможет вам поймать проблему в тестах. В C++ operator* всегда является небезопасным доступом, и программист в ответе за проверку. Хотите чтобы стандартная библиотека делала проверку за вас - используйте value(). Если менять устоявшееся правило только для одного класса то будет только хуже - возникнет путанница.

Ну и наконец, это же C++. Не нравится стандартный std::expected - напишите и используйте свой, никто не заставляет пользоваться тем, что не подходит под ваши конкретные нужды.

Тип будет const char*

Фактически будем итерироваться по диапазону {"Hello", ", ", "world"}

Предложение на as/is было встречено положительно, но в C++23 pattern matching уже не попадёт. Моё предчувствие - идея пройдёт в стандарт C++26. А за прогрессом идеи можно наблюдать тут https://github.com/cplusplus/papers/issues/1064

А по второй идее предложение ещё не рассматривали. Но мне кажется, что шансы на принятие - минимальны

И сегодня как назло сломался сертификат для https://wg21.link/ . Ответственные предупреждены, скоро починят и ссылки на предложения заработают.

Всё так. Корутины и асинхронность - это не серебряная пуля для всех проблем, а лишь возможность экономить CPU на IO-bound приложениях.

В случае баз данных фреймворки зачастую предоставляют другие способы улучшить это место. Например userver использует бинарный протокол для общения с PostgreSQL (не тот, что по умолчанию в libpq), автоматическое превращение всех запросов в prepared statements, динамическое управление пулами; на подходе использование порталов и pipelining. Разумееется это всё полностью не убирает бутылочное горлышко, но позволяет его немного расширить

Проблема возникает на порядок раньше. У неё даже название есть - C10k, так что уже при подходе к 10к RPS вам нужен асинхронный движок (с корутинами или без).

В целом приблизительно всё так и дружится.

Но помимо интеграции с корутиновым движком нужны ещё метрики, логи, плагин для кодогенерации gRPC C++ кода, документация... В общем, работы на пару-тройку месяцев

Если все потоки яростно дерутся за общий ресурс то во всей красе проявляется закон Амдала:

Допустим 50% времени поток проводит в критической секции. Соответственно на системе с 100 ядрами вы получите ускорение 1÷(0.5 +(0.5÷100)) ~= 2. Всего в 2 раза быстрее будет работать ваш код на 100 ядерной машине, чем на одноядерной.

Ускорение мьютекса тут ситуацию не улучшит, нужно менять сам алгоритм, например использовать специальные очереди, так же предоставляемые фреймворком.

Если же в критической секции приложение проводит мало времени, то шансы столкновения сразу множества потоков на мьютексе малы и мы получаем картину из верхней части таблички. Со временем, мы наверное займёмся оптимизацией этого случая и сделаем некий backoff, но пока что это не в приоритете.

Это очень интересный вопрос.

В теории, так можно жить и даже можно сдлеать интересные оптимизации, экономящие CPU. Приблизительно так работает Python3, где основная часть программы однопоточная, а масштабирование происходит через запуск новых инстансов приложения.

На практике так жить зачастую не получается из-за несовершенства ОС (не для всего есть асинхронные вызовы) и из-за несовершенства драйверов к различным базам данных и протоколов передачи данных. В том же Python3 драйвер для gRPC порождает свой собственный поток.

С идеальным однопоточным режимом есть и недостаток - да, можно немного оптимальнее загрузить CPU, но страдает latency. Если к вам приходит запрос, обработку которого можно разложить на две несвязанные задачи по 400ms, то в однопоточном режиме вы вернёте ответ через 400ms+400ms. В многопоточном режиме задачи обработаются на разных потоках и ответ вы вернёте через 400ms.

Исключения и корутины друг другу не мешают

С базами данных тоже можно работать асинхронно. Тогда ваш пример при использовании асинхронности с callback наподобие boost::asio будет выглядеть так:

socket.receive(
  [*this, socket](std::vector<unsigned char> data) {
    database->execute("SELECT hash FROM passwords WHERE id=$1", data,
      [*this](auto answer) {
        socket.send(answer[0][0] ? "OK" : "FAIL");
      });
  });

Поток не будет блокироваться на 200ms. Вместо этого, пока не получен ответ от базы данных, поток будет обрабатывать другие запросы. А когда ответ от базы данных будет получен, callback [*this](auto answer) будет перемещён в очередь готовых к выполнению задач и выполнен.

Если добавить корутины, то вся подкапотная магия с асинхронностью остаётся, но код начинает выглядеть как синхронный:

auto data = socket.receive();
auto answer = database->execute("SELECT hash FROM passwords WHERE id=$1", data);
socket.send(answer[0][0] ? "OK" : "FAIL");

Да, именно так

Да, это место в корутинах аналогично поведению при использовании std::thread. Чтобы захватить объект по копии надо использовать [*this]

Однако с корутинами этих проблем наоборот становится сильно меньше, по сравнению с обычным асинхронным кодом, так как меньше callbacks.

Код с корутинами:

auto data = socket.receive();
this->process(data);
socket.send(data);
this->log_ok();


Аналогичный код без корутин:

socket.receive(
  [*this, socket](std::vector<unsigned char> data) {
    process(data);
    socket.send(data, [*this]() {
      this->log_ok();
    });
  });

Насколько я помню, в c++ корутинах есть глюки с захватом переменных в capture list.

Можете подробнее рассказать, о каких именно глюках речь?

Интересно, а есть ли смысл вообще изобретать асинхронный мьютекс, если
во время выполнения программы почти наверняка будет выделение памяти,
что по сути тот же самый мьютекс

Что касается выделения памяти - то всё зависит от вашего аллокатора. Современные аллокаторы внутри как правило lock-free или имеют крайне низкий contention на мьютексах. А вот в пользовательском коде асинхронный мьютекс крайне полезен - особенно когда у пользователей фреймворка получается критическая секция приличных размеров. Проверено обстрелами :-)

Вы о каком релизе и о какой декларации говорите? Поделитесь пожалуйста ссылкой, я ни о чём подобном не слышал

Люди, пишущие стайлгайды, и люди, работающие над стандартом, - это одни и те же люди.

Информация

В рейтинге
85-й
Откуда
Россия
Работает в
Зарегистрирован
Активность