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

C++ designing a container with expiration/duration

本文关键字:过期 持续时间 一个 C++      更新时间:2023-10-16

我需要提供一个小的关联数组(比如TimedMap),其中键和值的类型是不确定的。这个关联数组的所有元素都有一个固定的持续时间。例如,假设键类型为"int",值类型为"Foo",持续时间为5秒。如果我插入(0,Foo实例),那么在5秒钟后,该值应该会消失。

由于我的同事将使用这个关联数组,他不想为数组的获取/设置操作锁定/解锁,并且假设我别无选择。除了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.pp:

#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) = %sn", key, (*ptr)->what());
          }
          else {
            fprintf(stderr, "main: key(%d) was not theren", key);
            break;
          }
        }
        //break;
        usleep(100000);
        //sleep(1);
      }
    }
    m.clear();
  }
  return 0;
}

我建议有一个第三个字段,它是元素的寿命

您需要一个friend方法,该方法在每次解析时都会唤醒并缩短生存时间。当使用寿命为零时,元素将被移除。

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

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

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

编辑1:
由于life-time任务可以修改容器,因此应将包含标记为volatile