Несколько читателей, блокировка одного писателя в OpenMP
Существует объект, совместно используемый несколькими потоками для чтения и записи, и мне нужно реализовать класс с блокировкой чтения-записи, которая имеет следующие функции:
- Он может быть объявлен занятым одним и не более чем одним потоком. Любые другие потоки, которые попытаются занять его, будут отклонены и будут продолжать выполнять свою работу, а не блокироваться.
- Любой из потоков может запрашивать, занят ли объект самим собой или другими в любое время, за исключением времени, когда он объявлен занятым или освобожденным.
- Только владелец объекта может освободить его владельца, хотя другие могут попытаться сделать это также. Если он не является владельцем, операция освобождения будет отменена.
- Производительность должна быть тщательно продумана.
Я делаю работу с OpenMP, так что я надеюсь реализовать блокировку, используя только API в OpenMP, а не POSIX или так далее. Я прочитал этот ответ, но есть только решения для реализаций стандартной библиотеки C++. Поскольку смешивание OpenMP со стандартной библиотекой C++ или моделью потоков POSIX может замедлить работу программы, мне интересно, есть ли хорошее решение для OpenMP?
Я пытался так, иногда это работало нормально, но иногда он падал, а иногда был заблокирован. Мне также трудно отлаживать.
class Element
{
public:
typedef int8_t label_t;
Element() : occupied_(-1) {}
// Set it occupied by thread @myThread.
// Return whether it is set successfully.
bool setOccupiedBy(const int myThread)
{
if (lock_.try_lock())
{
if (occupied_ == -1)
{
occupied_ = myThread;
ready_.set(true);
}
}
// assert(lock_.get() && ready_.get());
return occupied_ == myThread;
}
// Return whether it is occupied by other threads
// except for thread @myThread.
bool isOccupiedByOthers(const int myThread) const
{
bool value = true;
while (lock_.get() != ready_.get());
value = occupied_ != -1 && occupied_ != myThread;
return value;
}
// Return whether it is occupied by thread @myThread.
bool isOccupiedBySelf(const int myThread) const
{
bool value = true;
while (lock_.get() != ready_.get());
value = occupied_ == myThread;
return value;
}
// Clear its occupying mark by thread @myThread.
void clearOccupied(const int myThread)
{
while (true)
{
bool ready = ready_.get();
bool lock = lock_.get();
if (!ready && !lock)
return;
if (ready && lock)
break;
}
label_t occupied = occupied_;
if (occupied == myThread)
{
ready_.set(false);
occupied_ = -1;
lock_.unlock();
}
// assert(ready_.get() == lock_.get());
}
protected:
Atomic<label_t> occupied_;
// Locked means it is occupied by one of the threads,
// and one of the threads might be modifying the ownership
MutexLock lock_;
// Ready means it is occupied by one the the threads,
// and none of the threads is modifying the ownership.
Mutex ready_;
};
Атомарная переменная, мьютекс и блокировка мьютекса реализованы с помощью инструкций OpenMP следующим образом:
template <typename T>
class Atomic
{
public:
Atomic() {}
Atomic(T&& value) : mutex_(value) {}
T set(const T& value)
{
T oldValue;
#pragma omp atomic capture
{
oldValue = mutex_;
mutex_ = value;
}
return oldValue;
}
T get() const
{
T value;
#pragma omp read
value = mutex_;
return value;
}
operator T() const { return get(); }
Atomic& operator=(const T& value)
{
set(value);
return *this;
}
bool operator==(const T& value) { return get() == value; }
bool operator!=(const T& value) { return get() != value; }
protected:
volatile T mutex_;
};
class Mutex : public Atomic<bool>
{
public:
Mutex() : Atomic<bool>(false) {}
};
class MutexLock : private Mutex
{
public:
void lock()
{
bool oldMutex = false;
while (oldMutex = set(true), oldMutex == true) {}
}
void unlock() { set(false); }
bool try_lock()
{
bool oldMutex = set(true);
return oldMutex == false;
}
using Mutex::operator bool;
using Mutex::get;
};
Я также использую блокировку, предоставляемую OpenMP, в качестве альтернативы:
class OmpLock
{
public:
OmpLock() { omp_init_lock(&lock_); }
~OmpLock() { omp_destroy_lock(&lock_); }
void lock() { omp_set_lock(&lock_); }
void unlock() { omp_unset_lock(&lock_); }
int try_lock() { return omp_test_lock(&lock_); }
private:
omp_lock_t lock_;
};
Кстати, я использую gcc 4.9.4 и OpenMP 4.0, на x86_64 GNU/Linux.