Я работаю над проектом, который требует использования определенных абстракций ОС, и мне нужно реализовать блокировку чтения-записи, используя их семафор и мьютекс. У меня на данный момент есть настройка в формате:
class ReadWriteLock
{
public:
ReadWriteLock(uint32_t maxReaders);
~ReadWriteLock();
uint32_t GetMaxReaders() const;
eResult GetReadLock(int32_t timeout);
eResult GetWriteLock(int32_t timeout);
eResult Unlock();
private:
uint32_t m_MaxReaders;
Mutex* m_WriterMutex;
Semaphore* m_ReaderSemaphore;
};
В этой реализации мне нужно использовать этот метод Unlock, чтобы либо разблокировать писатель и освободить все слоты семафора читателя, либо просто освободить слот семафора читателя, однако я изо всех сил не могу придумать реализацию, которая будет работать во всех случаев. Как я могу сделать эту работу в данной настройке? Я знаю, что это возможно, поскольку POSIX смог реализовать универсальный метод разблокировки в своей реализации, но я не могу найти никаких указаний на то, как это было сделано, поэтому был бы признателен за любую информацию, которой могут поделиться люди.
Обратите внимание, что я не могу использовать C ++ 11 или другие примитивы ОС.
Ну, определим две функции UnlockRead
а также UnlockWrite
,
Я считаю, что вам не нужны оба доступа (запись / чтение) в одно и то же время в одном и том же месте. Итак, я предлагаю два других класса для блокировки доступа:
class ReadWriteAccess
{
public:
ReadWriteAccess(uint32_t maxReaders);
~ReadWriteAccess();
uint32_t GetMaxReaders() const;
uint32_t GetMaxReaders() const;
eResult GetReadLock(int32_t timeout);
eResult GetWriteLock(int32_t timeout);
eResult UnlockWrite();
eResult UnlockRead();
private:
uint32_t m_MaxReaders;
Mutex* m_WriterMutex;
Semaphore* m_ReaderSemaphore;
};
И есть отдельные классы для чтения и записи блокировки и использования RAII быть всегда в безопасности:
class ReadLock
{
public:
ReadLock(ReadWriteAccess& access, int32_t timeout) : access(access)
{
result = access.GetReadLock(timeout);
}
eResult getResult() const { return result; }
~ReadLock()
{
if (result)
access.UnlockRead();
}
private:
ReadWriteAccess& access;
eResult result;
};
и используйте так:
T someResource;
ReadWriteAccess someResourceGuard;
void someFunction()
{
ReadLock lock(someResourceGuard);
if (lock.getResult())
cout << someResource; // it is safe to read something from resource
}
Конечно, очень похожая реализация, которую вы можете легко написать самостоятельно для WriteLock
Поскольку OP настаивал на том, чтобы в комментариях была «одна» разблокировка — пожалуйста, учтите недостатки:
Предположим, реализован некий стек последних вызовов функций Lock:
class ReadWriteLock
{
public:
ReadWriteLock(uint32_t maxReaders);
~ReadWriteLock();
uint32_t GetMaxReaders() const;
eResult GetReadLock(int32_t timeout)
{
eResult result = GetReadLockImpl(timestamp);
if (result)
lockStack.push(READ);
}
eResult GetWriteLock(int32_t timeout)
{
eResult result = GetWriteLockImpl(timestamp);
if (result)
lockStack.push(WRITE);
}
eResult Unlock()
{
LastLockMode lockMode = lockStack.top();
lockStack.pop();
if (lockMode == READ)
UnlockReadImpl();
else
UnlockWriteImpl();
}
private:
uint32_t m_MaxReaders;
Mutex* m_WriterMutex;
Semaphore* m_ReaderSemaphore;
enum Mode { READ, WRITE };
std::stack<Mode> lockStack;
};
Но вышесказанное будет работать только в однопоточном приложении. И однопоточное приложение никогда не нуждается в каких-либо блокировках.
Итак, у вас должен быть многопоточный стек, например:
template <typename Value>
class MultiThreadStack
{
public:
void push(Value)
{
stackPerThread[getThreadId()].push(value);
}
Value top()
{
return stackPerThread[getThreadId()].top();
}
void pop()
{
stackPerThread[getThreadId()].pop();
}
private:
ThreadId getThreadId() { return /* your system way to get thread id*/; }
std::map<ThreadId, std::stack<Value>> stackPerThread;
};
Так что используйте это MultiThreadStack
не std :: stack в ReadWriteLock
,
Но, std::map
выше понадобится ReadWriteLock
заблокировать доступ к нему из нескольких потоков — так что, либо вы знаете все свои потоки, прежде чем начать использовать этот материал (предварительная регистрация), либо вы столкнетесь с той же проблемой, как описано Вот. Так что мой совет — если можешь — поменяй свой дизайн.
При успешном получении блокировки тип известен: либо у вас работает много читателей, либо только один писатель, у вас не может быть и читателей, и писателей, работающих с правильно полученной блокировкой.
Поэтому достаточно сохранить текущий режим блокировки, когда lock
вызов успешен и все последующие unlock
вызовы (потенциально много в случае, если разрешение на чтение было предоставлено, только один действительно, если была запрошена блокировка записи) будут из этого режима.