C++ ScopeTryLock

В прошлый раз мы рассмотрели что такое и как работает ScopeLock. Сегодня мы разовьем эту идею далее.

Пытливый читатель наверное уже догадался о чем пойдет речь и может быть даже прикинул как это может быть реализовано. Но не будем обращать на него внимания и продолжим.


Иногда, если блокировка уже кем-то захвачена, мы не хотим ждать пока она освободиться. Например, в pthreads для этого есть некоторые функции:

int pthread_mutex_trylock(pthread_mutex_t* mutex);
int pthread_spin_trylock(pthread_spinlock_t* lock);

Подобная возможность есть практически в любом API связанном с блокировками. Поэтому неплохо бы уметь с ними работать с стиле RAII.

Начнем, по традиции, с наивной реализации:

class ScopeTryLock {
public:
inline ScopeTryLock(LockObj& lockobj)
: _lockobj(lockobj)
{
_lockobj.try_lock(); // новый метод :)
}

inline ~ScopeTryLock() {
_lockobj.unlock();
}

protected:
LockObj& _lockobj;

private:
ScopeTryLock(const ScopeTryLock&);
void operator=(const ScopeTryLock&);
};

Первое, что бросается в глаза, это невозможность понять, захватили мы блокировку или нет. Это легко исправить:

class ScopeTryLock {
public:
inline ScopeTryLock(LockObj& lockobj)
: _lockobj(lockobj)
{
_locked = _lockobj.try_lock();
}

inline ~ScopeTryLock() {
_lockobj.unlock();
}

inline bool is_locked() const {
return _locked;
}

protected:
bool _locked;
LockObj& _lockobj;

private:
ScopeTryLock(const ScopeTryLock&);
void operator=(const ScopeTryLock&);
};

Уже лучше. Теперь мы можем проверить захватили мы блокировку или нет:

bool try_use_shared_resource_concurrently() {
ScopeTryLock scope_lock(lockobj);
if (!lockobj.is_locked()) return false;
use_shared_resource();
return true;
}

Однако тут есть еще одна проблема, которую очень сложно отладить, если не заметить. Освобождение блокировки безусловное и даже если мы не захватили ее мы освободим ее за кого-то другого. Это может привести к огромному количеству сложнопредсказуемых проблем. Что ж, исправим это:

class ScopeTryLock {
public:
inline ScopeTryLock(LockObj& lockobj)
: _lockobj(lockobj)
{
_locked = _lockobj.try_lock();
}

inline ~ScopeTryLock() {
if (_locked)
_lockobj.unlock();
}

inline bool is_locked() const {
return _locked;
}

protected:
bool _locked;
LockObj& _lockobj;

private:
ScopeTryLock(const ScopeTryLock&);
void operator=(const ScopeTryLock&);
};

Voila! ScopeTryLock готов к употреблению. И это получилось быстрее, чем я думал.


В стандартной библиотеке С++ начиная с версии C++11 имеется примитив std::lock_guard, который может становиться как ScopeLock, так и ScopeTryLock, а так же адаптироваться к состоянию блокировки. Рекомендую вам использовать его если это возможно.

Happy coding!