Можно ли создать мьютекс из C++20 std::atomic без вращения, чтобы избежать использования после освобождения при удаленииC++

Программы на C++. Форум разработчиков
Ответить
Anonymous
 Можно ли создать мьютекс из C++20 std::atomic без вращения, чтобы избежать использования после освобождения при удалении

Сообщение Anonymous »

Прежде чем перейти к основному вопросу, я предполагаю, что std::mutex::unlock() перестанет трогать этот объект, как только он переведет мьютекс в состояние, в котором другой поток может его заблокировать, даже если вызов unlock() еще не вернулся. Другими словами, следующий код C++ является правильным:

Код: Выделить всё

#include 
#include 
#include 

struct RefCount {
std::mutex m_;
std::size_t c_{};

// Always construct on heap
static RefCount *make() { return new RefCount; }
virtual ~RefCount() = default;

void incref()
{
m_.lock();
++c_;
m_.unlock();
}

void decref()
{
m_.lock();
auto c = --c_;
m_.unlock();
if (!c)
delete this;
}

protected:
RefCount() noexcept = default;
};
В частности, не должно быть неопределенного поведения, если два потока содержат счетчик ссылок и вызывают decref() - первый поток функционально разблокирует мьютекс, даже если unlock() еще не вернулся, затем второй поток разблокирует мьютекс и уничтожает объект, включая мьютекс, и только тогда первый поток возвращается из вызова unlock().
К сожалению, примеры, подобные приведенным выше, будут похоже, препятствует прямым мьютексам на основе фьютексов, реализованным с помощью std::atomic. Обычно используется три состояния (разблокировано, заблокировано и заблокировано с помощью ожидающих), что может выглядеть следующим образом:

Код: Выделить всё

struct BadMutex {
static constexpr uint8_t kUnlocked = 0;
static constexpr uint8_t kLocked = 1;
static constexpr uint8_t kLockedWantNotify = 2;
std::atomic_uint8_t state_{kUnlocked};

void lock()
{
for (;;) {
auto s = state_.exchange(kLocked, std::memory_order_acquire);
if (s != kUnlocked)
s = state_.exchange(kLockedWantNotify, std::memory_order_acquire);
if (s == kUnlocked)
return;
state_.wait(kLockedWantNotify);
}
}

void unlock()
{
if (state_.exchange(kUnlocked, std::memory_order_release) == kLockedWantNotify)
// UB if BadMutex destroyed here
state_.notify_all();
}
};
К сожалению, если вы подключите BadMutex к RefCount выше, вы можете получить неопределенное поведение, если предпоследний поток decref() вытесняется прямо перед тем, как state_.notify_all() и BadMutex уничтожаются, поскольку вызов метода для уничтоженного объекта является UB.
Обратите внимание, что использование необработанного объекта Системный вызов FUTEX_WAKE подойдет, потому что нет ничего страшного в том, чтобы отправить ложное пробуждение или даже вызвать FUTEX_WAKE в неотображенной памяти (просто получите EFAULT, который вы можете игнорировать). Итак, на практике этот UB не должен иметь большого значения, но, конечно, история не любит преднамеренный UB.
Итак, мой следующий вопрос: могу ли я что-нибудь с этим сделать? Лучшее, что я придумал, — это вращаться во время разрушения, но это оскорбительно, когда приходится вращаться, когда у нас есть фьютексы, специально разработанные для предотвращения вращения. Лучшее, что я могу придумать, это что-то вроде этого:

Код: Выделить всё

struct UglyMutex {
static constexpr uint8_t kUnlocked = 0;
static constexpr uint8_t kLocked = 1;
static constexpr uint8_t kLockedWantNotify = 2;
std::atomic_uint8_t state_{kUnlocked};
std::atomic_uint16_t unlocking_{0};

~UglyMutex()
{
while (unlocking_.load(std::memory_order_acquire))
;
}

void lock()
{
for (;;) {
auto s = state_.exchange(kLocked, std::memory_order_acquire);
if (s != kUnlocked)
s = state_.exchange(kLockedWantNotify, std::memory_order_acquire);
if (s == kUnlocked)
return;
state_.wait(kLockedWantNotify);
}
}

void unlock()
{
unlocking_.fetch_add(1, std::memory_order_relaxed);
if (state_.exchange(kUnlocked, std::memory_order_release) == kLockedWantNotify)
state_.notify_all();
unlocking_.fetch_sub(1, std::memory_order_release);
}
};
Может быть, мне не хватает какого-то другого трюка, который я мог бы использовать, чтобы сделать это без вращения - возможно, с помощью std::atomic_ref?
Есть ли надежда, что будущая версия C++ может предоставить безопасный способ уведомления об уничтоженных атомах?
Есть ли какая-то польза от языка, создающего этот UB, или это в основном недостаток спецификации языка, в котором атомики не раскрывают полную выразительность базовые фьютексы, на которых они реализованы?

Подробнее здесь: https://stackoverflow.com/questions/798 ... g-to-avoid
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C++»