Несколько читателей, блокировка одного писателя в 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.

0 ответов

Другие вопросы по тегам