понедельник, 27 июня 2022 г.

Реализация мьютекса в libstdc++

При общении с коллегами возник вопрос, на сколько затратна операция использования std::mutex в libstdc++.


В Linux mutex работает через системный вызов futex, который освобождает процессор и добавляет поток в очередь, если мьютекс занят.

Futex - это сокращение над Fast userspace mutex. Смысл в том, что перед тем как выполнять дорогостоящее обращение к ядру, выполняется проверка флага с использованием атомарных операций, и только если мьютекс действительно занят, то текущий вызов будет поставлен в очередь, которую предоставляет и обслуживает ядро.

Всё что сказано выше - пока просто справочная нформация, которую можно легко загуглить. Интересней другое: если флаг мьютекса занят, то выполняется ли при этом так называемый SPIN WAIT wait перед обращением к ядру.

Такой подход используется, например, в библиотеке .Net framework.

Под Windows std::mutex реализован с использованием spin wait, при этом блокировка крутится заданное число циклов (~4000), и уже по истечении этого времени поток засыпает.

Однако, как выяснилось, в libstdc++ механизм spin wait не реализован. В коде разбросаны комментарии вида:

// TODO Spin-wait first.

В мануале также нет информации по поводу spin wait.

Таким образом, до тех пор пока потоки не конлфиктуют за право владения объектом std::mutex, этот механизм работает крайне быстро и только в user space. Но вот когда начинают возникать блокировки, то сразу появляется необходимость обращения к ядру. Такие обращения сами по себе уже вызывают 10 тыс. циклов CPU. Поэтому не безосновательно принято считать использование мьютексов дорогостоящей операций.

При работе с мьютексами не стоит забывать два основных правила:

- Код под блокировкой мьютекса лучше всего свести к минимуму.

- Находясь пдо блокировкой, не стоит вызывать методы "чужих" объектов.