C ++设计一个具有到期/持续时间的容器

时间:2013-07-14 18:26:07

标签: c++ pthreads containers deadlock

我需要提供一个小的关联数组(比如TimedMap),其中key和value类型没有决定。该关联数组对其所有元素具有固定的持续时间。例如,假设键类型为'int',值类型为'Foo',持续时间为5秒。如果我插入(0,Foo实例),那么在5秒后,该值应该消失。

由于我的同事将使用这个关联数组,不想为数组的get / set操作锁定/解锁,并且假设我别无选择。除了boost :: shared_ptr之外,我不允许出于某种原因使用boost。

TimedMap实例应该只使用少数几个地方,所以我决定为每个实例创建一个线程,然后定期检查所有元素,以便它可以删除键值对,如果它已过期。

这是TimedMap的用户将使用的方式:

typedef TimedMap<int, boost::shared_ptr<Foo> > TMAP;
TMAP m(5);      // each key, value pair will have 5 seconds lifetime.

m.set(0, boost::shared_ptr<Foo>(new Foo()));

{ 
  TMAP::Ptr ptr = m.get(0);  // index 0, and value is locked at this time
  if (ptr) { // if it was in the map,
    // use (*ptr).xxx() to access Foo::xxx.
  }
} // now, 'ptr' is gone, so the lock will be gone.

现在,除了线程之外,我想我实现了其余部分。 有两种类型的锁,每个TimedMap实例一个锁,每个元素都有一个锁,用于保护每个元素。

问题是,有时,当调用TimedMap实例析构函数时,主锁因未知原因处于锁定状态。我查了一会儿,一无所获。

我知道在标准容器的元素中放置一个锁,但我现在想不出更好的解决方案。

你可以帮我这个吗?要编译它,

$ g++ timedmap.cpp

我的用法,我无法缩短来源。

timedmap.h:

#include <map>
#include <stdexcept>

#include <string>

#include <string.h>
#include <pthread.h>
#include <errno.h>

#define M_LOCK(m)       ((m).lock())
#define M_UNLOCK(m)     ((m).unlock())
#define M_TRYLOCK(m)    ((m).trylock())

template <typename K, typename V>
class TimedMap {
  class MUTEX {
    ::pthread_mutex_t lck_;

  public:
    MUTEX(const MUTEX &m) {
      // copy CTOR
      int type = PTHREAD_MUTEX_DEFAULT;
      ::pthread_mutexattr_t attr;
      ::pthread_mutexattr_init(&attr);
      ::pthread_mutexattr_settype(&attr, type);
      ::pthread_mutex_init(&lck_, &attr);
      ::pthread_mutexattr_destroy(&attr);
    }

    MUTEX(int type = PTHREAD_MUTEX_DEFAULT) {
      ::pthread_mutexattr_t attr;
      ::pthread_mutexattr_init(&attr);
      ::pthread_mutexattr_settype(&attr, type);
      ::pthread_mutex_init(&lck_, &attr);
      ::pthread_mutexattr_destroy(&attr);
    }

    ~MUTEX() {
      int ret = ::pthread_mutex_destroy(&lck_);
      if (ret) {
        // pthread_mutex_destroy() failed
        if (ret == EBUSY) {
          // locked?
        }
        //abort();
      }
    }

    int lock() {
      int ret = pthread_mutex_lock(&lck_);
      if (ret) {                // lock failed
        abort();
      }
      return ret;
    }

    int unlock() {
      int ret = pthread_mutex_unlock(&lck_);
      if (ret) {                // unlock failed
        abort();
      }
      return ret;
    }

    bool trylock() {
      int ret = pthread_mutex_trylock(&lck_);
      if (ret != 0 && ret != EBUSY) { // trylock failed
        abort();
      }
      return ret ? false : true;
    }
  };

  struct TMENT {
    time_t tm_;
    MUTEX m_;
    V val_;

    TMENT() : tm_(time(0)), m_(), val_() {}
    TMENT(const V &val) : tm_(time(0)), m_(), val_(val) {
    }
    void refresh() { tm_ = time(0); }
    bool expired(time_t expiration) {
      time_t now = ::time(0);
      return (now - tm_) > expiration;
    }
  };

  typedef std::map<K, TMENT> map_type;

  typedef typename map_type::key_type key_type;
  typedef typename map_type::mapped_type mapped_type;
  typedef typename map_type::value_type value_type;

  TimedMap(const TimedMap &);
  map_type map_;
  MUTEX mlock_;
  int expiration_;
  bool refresh_;

public:
  explicit TimedMap(int expiration)
    : map_(), mlock_(PTHREAD_MUTEX_ERRORCHECK),
      expiration_(expiration), refresh_(false) {
  }

  ~TimedMap() {
    clear();
  }

  void clear() {
    M_LOCK(mlock_);
    typename map_type::iterator i = map_.begin();
    while (i != map_.end()) {
      TMENT &ent = (*i).second;
      M_LOCK(ent.m_);
      M_UNLOCK(ent.m_);
      map_.erase(i++);
    }
    M_UNLOCK(mlock_);
  }

  class Ptr {
    TMENT *ent_;

    mutable bool owned_;

    bool yield() const {
      if (!owned_) {
        // Ptr can't yield ownership when it doesn't");
        abort();
      }
      owned_ = false;
      return true;
    }

    Ptr &operator=(const Ptr &ptr);

  public:
    Ptr(const Ptr &ptr) : ent_(ptr.ent_) {
      owned_ = ptr.yield();
    }
    Ptr() : ent_(0), owned_(false) {}

    Ptr(TMENT *ent) : ent_(ent), owned_(true) {}

    ~Ptr() {
      if (owned_)
        M_UNLOCK(ent_->m_);
    }

    V *operator->() {
      if (!owned_)
        throw std::out_of_range("not found");
      return &(ent_->val_);
    }

    V &operator*() {
      if (!owned_)
        throw std::out_of_range("not found");
      return ent_->val_;
    }

    operator bool() {
      if (owned_)
        return true;
      return false;
    }
  };

  void set(const key_type &k, const V &v) {
    M_LOCK(mlock_);
    typename map_type::iterator i = map_.find(k);
    if (i == map_.end()) {
      map_[k] = TMENT(v);
    }
    else {
      TMENT &ent = (*i).second;
      M_LOCK(ent.m_);
      ent.refresh();
      ent.val_ = v;
      M_UNLOCK(ent.m_);
    }
    M_UNLOCK(mlock_);
  }

  Ptr get(const key_type &k) {
    M_LOCK(mlock_);

    typename map_type::iterator i = map_.find(k);

    if (i == map_.end()) {
      // key not found
      M_UNLOCK(mlock_);
      return Ptr();
    }

    TMENT &ent = (*i).second;

    M_LOCK(ent.m_);

    if (ent.expired(expiration_)) {
      // key expired
      M_UNLOCK(ent.m_);
      return Ptr();
    }
    else if (refresh_)
      ent.refresh();

    M_UNLOCK(mlock_);

    // Intentionally leave the 'ent' as locked state.
    return Ptr(&ent);
  }
};

timedmap.cpp:

#include <cstdio>
#include <boost/shared_ptr.hpp>
#include <time.h>

#include "timedmap.h"

class Foo {
public:
  Foo() {
  }
  ~Foo() {
  }

  const char *what() {
    return "foo";
  }

};

typedef TimedMap<int, boost::shared_ptr<Foo> > TMAP;

int
main(void)
{
  {
    TMAP m(5);

    int key = 0;
    {
      m.set(key, boost::shared_ptr<Foo>(new Foo()));
    }

    bool loop = true;

    {
      while (loop) {
        {
          TMAP::Ptr ptr = m.get(key);

          if (ptr) {
            fprintf(stderr, "main: key(%d) = %s\n", key, (*ptr)->what());
          }
          else {
            fprintf(stderr, "main: key(%d) was not there\n", key);
            break;
          }
        }
        //break;
        usleep(100000);
        //sleep(1);
      }
    }
    m.clear();
  }
  return 0;
}

1 个答案:

答案 0 :(得分:0)

我建议使用第3个字段,即元素的生命周期

您需要使用friend方法唤醒每个解决时间并减少生命周期。当寿命达到零时,元素被移除。

您需要一个单独的执行线程来处理生命周期,因为您不想暂停(阻止)客户端的线程。

实施是特定于操作系统的,或涉及操作系统特定的功能。

此外,在生命周期过程处于活动状态时,使用互斥锁或其他机制来阻止客户端线程访问元素。

编辑1:
由于容器可以通过生命周期任务进行修改,因此包含应标记为volatile