使用pthread条件变量与读写锁

11

我正在寻找一种在C++中使用pthread rwlock结构和条件例程的方法。

我有两个问题:

第一:这是否可能?如果不能,为什么?

第二:为什么当前的POSIX pthread没有实现这种行为?

为了理解我的目的,我将解释我将如何使用它:我有一个生产者-消费者模型来处理一个共享数组。当数组为空时,消费者将使用cond_wait进行等待,但在读取某些元素时会使用rdlock。当添加(+signal)或从数组中删除元素时,生产者将使用wrlock。

使用rdlock而不是mutex_lock的好处是提高性能:使用mutex_lock时,多个读者将被阻塞,而使用rdlock时,多个读者将不会阻塞。

6个回答

8

我认为您所说的“conditions”是指“条件变量”,这两个东西是不同的。

不,您不能在等待条件变量时使用rwlock。我不知道为什么,但这是POSIX决定的方式。也许只是为了保持简单。

但是,您仍然可以通过使用仅带有互斥锁和2个条件变量而不使用POSIX rwlock来创建自己的rwlock类来获得所需的行为:

getReadLock():
     lock(mutex)
     while(array.empty())
         wait(readersCondVar, mutex)
     readers++;
     unlock(mutex)       

releaseReadLock():
     lock(mutex)
     if (--readers == 0)
           broadcast(writerCondVar, mutex) // or signal, if only 1 producer
     unlock(mutex)

readerThread:
     forever() {
         getReadLock()
         read()
         releaseReadLock()
      }

getWriteLock():
     lock(mutex)
     while(readers) {
         wait(writerCondVar, mutex)
     }

releaseWriteLock():
     broadcast(readersCondVar, mutex)
     unlock(mutex)

writerThread():
      forever() {
         getWriteLock()
         write()
         releaseWriteLock()
      }

简单易用,功能齐全。


很简单,而且做了我想要的事情!我想要一种重用pthread rwlock和condvar的方法,但我可能是错的 :-) - Doomsday

2

我有和你一样的需求。

这是我的解决方案:

封装 pthread_rwlock_t

class rwlock
{
public:
    rwlock()
    {
        pthread_rwlock_init(&_lock, nullptr);
    }

    ~rwlock()
    {
        pthread_rwlock_destroy(&_lock);
    }

    void read_lock()
    {
        pthread_rwlock_rdlock(&_lock);
    }

    void write_lock()
    {
        pthread_rwlock_wrlock(&_lock);
    }

    bool try_read_lock()
    {
        return pthread_rwlock_tryrdlock(&_lock) == 0;
    }

    bool try_write_lock()
    {
        return pthread_rwlock_trywrlock(&_lock) == 0;
    }

    void lock()
    {
        read_lock();
    }

    void try_lock()
    {
        try_read_lock();
    }

    void unlock()
    {
        pthread_rwlock_unlock(&_lock);
    }

private:
    pthread_rwlock_t _lock;
};

使用方法

rwlock lock;

std::condition_variable_any cond;

bool ready = false;

生产者

lock.write_lock();
...
if (!ready) {
    ready = true;
    cond.notify_all();
}
lock.unlock();

消费者

std::unique_lock<rwlock> lock_(lock);
while (!ready) {
    cond.wait(lock_, []{ return ready; });
}
...
ready = false;

1

C++0x正在获得多线程支持,该支持包括一种称为condition_variable_any的新类型:

class condition_variable_any
{
public:
    condition_variable_any();
    ~condition_variable_any();

    condition_variable_any(const condition_variable_any&) = delete;
    condition_variable_any& operator=(const condition_variable_any&) = delete;

    void notify_one();
    void notify_all();

    template <class Lock>
        void wait(Lock& lock);
    template <class Lock, class Predicate>
        void wait(Lock& lock, Predicate pred);

    template <class Lock, class Clock, class Duration>
        cv_status
        wait_until(Lock& lock,
                   const chrono::time_point<Clock, Duration>& abs_time);

    template <class Lock, class Clock, class Duration, class Predicate>
        bool
        wait_until(Lock& lock,
                   const chrono::time_point<Clock, Duration>& abs_time,
                   Predicate pred);

    template <class Lock, class Rep, class Period>
        cv_status
        wait_for(Lock& lock,
                 const chrono::duration<Rep, Period>& rel_time);

    template <class Lock, class Rep, class Period, class Predicate>
        bool
        wait_for(Lock& lock,
                 const chrono::duration<Rep, Period>& rel_time,
                 Predicate pred);
};

这里有一个关于如何实现condition_variable_any的解释:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2406.html#gen_cond_var

但在此链接中,它被命名为gen_cond_var。condition_variable_any的神奇之处在于它将等待具有lock()和unlock()成员的任何内容。一旦您拥有了condition_variable_any,那么您所需要的就是一个rwlock。上面的链接还介绍了shared_mutex和shared_lock,并展示了执行您想要的操作的示例代码:

std::tr2::shared_mutex mut;
std::gen_cond_var cv;

void wait_in_shared_ownership_mode()
{
    std::tr2::shared_lock<std::tr2::shared_mutex> shared_lk(mut);
    // mut is now shared-locked
    // ...
    while (not_ready_to_proceed())
        cv.wait(shared_lk);  // shared-lock released while waiting
    // mut is now shared-locked
    // ...
}   // mut is now unlocked

void wait_in_unique_ownership_mode()
{
    std::unique_lock<std::tr2::shared_mutex> lk(mut);
    // mut is now unique-locked
    // ...
    while (not_ready_to_proceed())
        cv.wait(lk);  // unique-lock released while waiting
    // mut is now unique-locked
    // ...
}   // mut is now unlocked

上述文件有些过时。这里有一个更为实用和详细的shared_mutex/shared_lock实现和描述:

http://howardhinnant.github.io/shared_mutex http://howardhinnant.github.io/shared_mutex.cpp

所有这些都是在POSIX pthreads之上实现的。我希望将共享锁定内容纳入C++技术报告(tr2)中,但当然不能保证。


0

为了解决Doomsday指出的问题,我们需要在grab_mutexwait_on_condition之间再次检查条件:

consumer() {

  get_readlock();
  if(array_empty()) {
    release_readlock();
    grab_mutex();
    if(array_empty()) {
       wait_on_condition();
    }
    release_mutex();
    get_readlock();
  }
  process_elements();
  release_readlock();
}  

0

根据您的需求,您只需要拥有1组rwlock和1组mutex/cond变量,伪代码(尽管您需要在posix cond变量上使用通常的循环)

consumer() {

  get_readlock();
  if(array_empty()) {
    release_readlock();
    grab_mutex();
    wait_on_condition();
    release_mutex();
    get_readlock();
  }
  process_elements();
  release_readlock();
}

producer()
{
  get_writelock();
  get_mutex();
  insert_elements();
  signal_condition();
  release_mutex();
  release_writelock();
}

我猜条件变量只能与互斥锁一起使用,因为等待或发出条件需要互斥性。

1
警告,这个解决方案可能会导致一个严重的问题:在第一次release_readlock和第一次grab_mutex之间,消费者可能会将手交给生产者,而生产者可以填充数组并发出信号给任何人。如果发生这种情况,当消费者实际上执行wait_on_condition时,它可能会被阻止,等待生产者已经发送的信号。 - Doomsday

0

在互斥锁和条件变量之上,实现了几个读写锁。选择任何一个并为您的自定义需求添加一些条件变量。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接