0

我需要提供一个小的关联数组(比如 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.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;
}
4

1 回答 1

0

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

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

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

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

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

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

于 2013-07-14T19:15:37.747 回答