Comments 40
while(_status == status::up) {
Это UB
Гонка
Во-первых, у компилятора могут оказаться основания полагать, что поле _status
в цикле не меняется, из-за чего цикл превратится в вечный. Кстати, бесконечный цикл — тоже UB, что разрешает компилятору корёжить программу дальше.
Во-вторых, значение status::up
может "застрять" в кеше процессора, из-за чего цикл сделает кучу холостых операций.
Если один поток записал в переменную некоторое значение — это ещё не означает, что другой поток именно это значение и прочитает.
Во-вторых, значение status::up может «застрять» в кеше процессора, из-за чего цикл сделает кучу холостых операций.
Это, кстати, распространённый миф, кэши процессора когерентны и если вы в одну кэш линию что-то записали, то она «разъедется» на все ядра.
Другое дело что никто не гарантирует что запись будет осуществлена в том месте где вы написали (процессор/компилятор вольны переставлять инструкции) или вообще осуществлена — никто же не сказал что это атомик, можно вообще запись в кэш/память выкинуть или на регистрах оставить.
Я уже так привык, что race condition всегда называют своим термином, что и думать забыл о нём, как об UB.
Ну, для такого и придумали volatile. Что впрочем не отменяет необходимость синхронизации чтения/записи
Так-то volatile != atomic. Но да, вы правы, использовался он чаще именно для этого
К сожалению, x86 и друзья являются «strong ordered» архитектурами и поймать instruction reorder там сложно (вот хорошая статья, демонстрирующая, как его всё-таки словить), а значит ваш супер-мега «локфри» на волатайлах будет успешно «работать», возможно в продакшне и не один год.
Если да, то у меня для вас плохие новости — либо у вас где-то есть барьер, который маскирует проблему, либо вы просто ещё не нашли багу.
Ключевое слово volatile не спасает от проблем многопоточности на relaxed-ordered архитектурах, потому что инструкции может переставлять процессор, а не компилятор (точнее, это могут делать оба, но volatile запрещает только второму).
Другое дело, что стандарт «нормального языка» ничего не говорит о многопоточности и memory ordering (поправьте, если ошибаюсь), а значит оставляет вас и компилятор в серой зоне — компилятор может вам помочь и напихать барьеров памяти, видя volatile, а может и не помогать и тогда ваш код будет содержать трудноотлаживаемую багу.
Так это же хорошо! Багу, не пойманную никем в первые недели/месяцы, можно считать несуществующей и пренебречь ей в принципе. Кто вам сказал, что такому коду/проекту когда-нибудь в принципе понадобится запуск на иной архитектуре?
Почему в многопоточном сервере я не вижу ни одного мьютекса или другого примитива синхронизации?
Зато я сходу вижу гонку между TcpServer::stop
и TcpServer::handlingLoop
...
Почему в многопоточном сервере я не вижу ни одного мьютекса или другого примитива синхронизации?
Блокировки вообще не обязательны для многопоточного приложения. Достаточно CAS и других атомарных операций.
Как-то раз встала задача по написанию простого и быстрого многопоточного TCP/IP сервера на C++ и при этом, чтобы работал из под Windows и Linux без требования как-либо изменять код за пределами класса самого сервера.
Можно нескромный вопрос: "встала задача" по учебе (в качестве лабораторной или курсовой) или же по работе?
bool TcpServer::Client::sendData(const char* buffer, const size_t size) const {
if(send(socket, buffer, size, 0) < 0) return false;
return true;
}
А если send вернет больше 0 или 0?
for(std::list<std::thread::id>::iterator id_it = client_handling_end.begin (); !client_handling_end.empty(); id_it = client_handling_end.begin())
for(std::list<std::thread>::iterator thr_it = client_handler_threads.begin (); thr_it != client_handler_threads.end (); ++thr_it)
if(thr_it->get_id () == *id_it) {…
Может лучше map юзать? Квадратичная сложность — так себе идея
100-пудово. Больше 10 лет тому назад в качестве домашнего задания при собеседовании получил подобное задание. К тому времени во всю использовал уже boost.asio.
Начал делать, думал за пару вечеров управиться. Но писать плохо не хотелось. Гляжу, сроки задания едут, а выходит что-то типа упрощенного asio. А тут и другой работодатель меня и нашел :)
while (size == 0) size = client.loadData();
->
while (!(size == client.loadData()));
А ещё, как я непрофессионально вижу, можно платформенные дефайны поставить в один конструктор, чтобы в целом меньше кода было
Кроссплатформенный многопоточный TCP/IP сервер на C++