Код: Выделить всё
#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;
};
К сожалению, примеры, подобные приведенным выше, будут похоже, препятствует прямым мьютексам на основе фьютексов, реализованным с помощью 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();
}
};
Обратите внимание, что использование необработанного объекта Системный вызов 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);
}
};
Есть ли надежда, что будущая версия C++ может предоставить безопасный способ уведомления об уничтоженных атомах?
Есть ли какая-то польза от языка, создающего этот UB, или это в основном недостаток спецификации языка, в котором атомики не раскрывают полную выразительность базовые фьютексы, на которых они реализованы?
Подробнее здесь: https://stackoverflow.com/questions/798 ... g-to-avoid
Мобильная версия