锁管理器的实现

锁管理器LockManager用于解决这样的问题:

我们需要一个锁对应一种资源,但是需要上锁的资源的种类可能非常的多,或者甚至是未知的。 这种情况下,我们不可能一次初始化所有的锁。如果不能对这些未知资源上锁的话,就不可能实现对这些资源的互斥访问了。

举个例子,有一张无限的地图,玩家可以在在地图上按照坐标随意移动。需要对每个坐标锁对应的资源上锁。

解决办法如下:

1. 首先对资源做唯一标识。我采用的是一个字符串来标识一个资源。 具体例子里就是坐标"x,y"

2. 一个字符串对应一把锁,由lockManager来控制锁的创建。可以想见,主要是一个key=>lock的map

 

class Lock {
public:
    typedef boost::shared_ptr<Lock> ptr;

public:
    Lock(const std::string& key)
        : m_locked(false), m_key(key)
    {}

    ~Lock();
    void lock();
    void unlock();

private:
    Mordor::FiberMutex m_mutex;
    bool m_locked;
    std::string m_key;
};

class ScopedLock {
public:
    ScopedLock() : m_locked(false) {}
    ScopedLock(Lock::ptr lock);
    ScopedLock& operator=(Lock::ptr lock);
    ~ScopedLock();
    void lock();
    void unlock();
private:
    Lock::ptr m_lock;
    bool m_locked;
};

class LockManager {
private:
    class Deleter {
    public:
        void operator()(Lock* timerLock);
    };
public:
    typedef boost::shared_ptr<LockManager> ptr;
    typedef std::map<std::string, Lock::ptr> LOCKS;
    LockManager() {}
    // acquire a lock on key,
    Lock::ptr lock(const std::string& key);
    void unlock(const std::string& key);

private:
    LOCKS m_locks;
    Mordor::FiberMutex m_mutex;
}; 

其中Lock是对我们项目中的mutex的一个简单封装。mutex可以看做是一个pthread_mutex的替代品

 

 我需要实现一个ScopedLock的功能,有两种选择:

1. 用shared_ptr的Deleter来实现。lockManager的lock() 上锁,然后返回一个 设置了特定Deleter的shared_ptr

   

Lock::ptr LockManager::lock(const std::string& key) {
    FiberMutex::ScopedLock checkLock(m_mutex);
    LOCKS::iterator it = m_locks.find(key);
    if (it != m_locks.end()) {
        it->second->lock();
        return boost::shared_ptr<Lock>(&*(it->second), Deleter());
    } else {
        Lock::ptr newLock(new Lock(key));
        m_locks[key] = newLock;
        newLock->lock();
        return boost::shared_ptr<Lock>(&*newLock, Deleter());
    }
}

    这个Deleter就负责unlock()。调用代码形如:

{
    Lock::ptr lock = lockManager->lock("key1");

    //do something ....

}// Deleter gets called here

 

 

 

2. 实现一个ScopedLock类

 

这里我选择了后者,原因如下:

 我们经常遇到这种情况,需要手动unlock(),而不是依赖于析构。假设我使用第一种实现。

代码如下:

Lock::ptr lock = lockManager->lock("key1");
do something...
if (...) {
  lock->unlock();
  do something else...
else {
  do soemthing else...
}
}// we do not want Deleter to unlock again. this may lead to undefiened behavior

 简单想到的解决方案是设置一个成员变量m_locked,在unlock()里如果m_locked==true,就不unlock了。

但是这样并不解决问题。如果

lock->unlock() 执行之后另一个线程得到执行并将该锁lock住,m_locked变为true, 那本线程里Deleter仍然将再次执行unlock. 这显然不是我想要的行为。

 根本原因是shared_ptr其实和原生指针在行为上完全一样。没有办法修改Deleter, 也没有办法给shared_ptr添加数据成员。添加一个m_locked到Lock对象是徒劳的,因为它对多个线程都可见。只有用一个线程本地的数据来标识才有用。

而实现一个ScopedLock就解决了这个问题。Lock::ptr是多线程共享可见的,而ScopedLock不是。


 

 另外,注意到我的ScopedLock有一个无参构造函数。这是有意义的。

我们都知道,一次上多个琐时,需要按照特定的顺序。一个函数里先A后B,另一个函数里先B后A。必然导致死锁。 对于lockManager,这也是一个问题。应为需要锁的资源本来就是未知的,我们怎么决定对多个这种资源上锁时的顺序呢? (按照字符串大小)

假定需要上两个锁。


std::string key1 = ...;

std::string key2 = ...;

if (key1 > key2)  {

 ScopedLock lock1(lockManager->lock(key1));

 ScopedLock lock2(lockManager->lock(key2));

  do something...

else {

 ScopedLock lock1(lockManager->lock(key2));

 ScopedLock lock2(lockManager->lock(key1));
  do something...

}

似乎没什么问题...但是考虑 两个分支里的do something可能是一样的代码,我可不想写重复的两份(也许可以用函数,但不是所有代码都可以或需要改成函数的)。

 

于是无参构造函数和operator=发挥功效了。。。

ScopedLock lock1;
ScopedLock lock2;

if (key1 > key2) {
  lock1 = lockManager->lock(key1);
  lock2 = lockManager->lock(key2);
else {
  lock2 = lockManager->lock(key2);
  lock1 = lockManager->lock(key1);
}

do something...

 

 至此,lockManager实现完毕。

 

posted on 2011-12-22 21:49  freestyleking  阅读(575)  评论(0编辑  收藏  举报

导航