锁管理器的实现
锁管理器LockManager用于解决这样的问题:
我们需要一个锁对应一种资源,但是需要上锁的资源的种类可能非常的多,或者甚至是未知的。 这种情况下,我们不可能一次初始化所有的锁。如果不能对这些未知资源上锁的话,就不可能实现对这些资源的互斥访问了。
举个例子,有一张无限的地图,玩家可以在在地图上按照坐标随意移动。需要对每个坐标锁对应的资源上锁。
解决办法如下:
1. 首先对资源做唯一标识。我采用的是一个字符串来标识一个资源。 具体例子里就是坐标"x,y"
2. 一个字符串对应一把锁,由lockManager来控制锁的创建。可以想见,主要是一个key=>lock的map
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
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(),而不是依赖于析构。假设我使用第一种实现。
代码如下:
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了。
但是这样并不解决问题。如果
根本原因是shared_ptr其实和原生指针在行为上完全一样。没有办法修改Deleter, 也没有办法给shared_ptr添加数据成员。添加一个m_locked到Lock对象是徒劳的,因为它对多个线程都可见。只有用一个线程本地的数据来标识才有用。
而实现一个ScopedLock就解决了这个问题。Lock::ptr是多线程共享可见的,而ScopedLock不是。
另外,注意到我的ScopedLock有一个无参构造函数。这是有意义的。
我们都知道,一次上多个琐时,需要按照特定的顺序。一个函数里先A后B,另一个函数里先B后A。必然导致死锁。 对于lockManager,这也是一个问题。应为需要锁的资源本来就是未知的,我们怎么决定对多个这种资源上锁时的顺序呢? (按照字符串大小)
假定需要上两个锁。
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 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) 编辑 收藏 举报