东阳的学习笔记
使用原则
互斥器保护了临界区
,任何时刻最多只有一个线程在此 mutex 划出的临界区内活动。单独使用 mutex 时,我们主要是为了保护共享数据
。
下面是使用 mutex 时的一些经验和原则:
主要原则有:
- 用
RAII
手法封装 mutex 的创建、销毁、加锁、解锁这四个操作。 - 只使用
非递归
的 mutex (即不可重入
的 mutex) - 不手动调用 lock() 和 unlock() 函数,一切交给栈上对象 GUARD 的
构造
和析构
函数来完成 - 在每次构造 GUARD 对象的时候,思考调用栈上已经持有的锁,防止因加锁顺序不同而导致死锁。
次要原则有:
- 不使用跨进程的 mutex ,进程间通信只用 TCP sockets。
- 加锁解锁在同一个线程,线程 a 不能去 unlock 线程 b 持有的锁(
RAII 自动保证
) - 别忘了解锁(
RAII 自动保证
) - 不重复解锁(
RAII 自动保证
) - 必要的时候可以考虑使用 PTHREAD_MUTEX_ERRORCHECK 来排错
只使用非递归的 mutex
递归锁与非递归锁的唯一区别在与:同一个线程可以重复对 recursive mutex 加锁,但是不能重复对 non-recursive mutex 加锁。
non-recursive mutex 不用考虑一个线程会被自己给锁死,这是一个优点,但是却也带来了问题:典型情况是,你以为拿到一个锁就能修改对象了,没想到外层代码已经拿到了锁。
见下面的示例代码,如果在 doit() 中调用了 post,将会导致:
- mutex 是非递归的,造成死锁
- mutex 是可递归的,
外层函数已经将 foo 锁死了,但在内层函数中却修改了foo, 这将导致外层函数的迭代器失效(偶尔),此时程序会 crash
。
void post(const Foo& f)
{
MutexLockGuard lock(mutex);
foos.push_back(f);
}
void traverse()
{
MutexLockGuard lock(mutex);
for (std::vector<Foo>::const_iterator it = foos.begin();
it != foos.end(); ++it)
{
it->doit();
}
}
一个死锁的例子
class Request;
class Inventory
{
public:
void add(Request *req)
{
muduo::MutexLockGuard lock(mutex_);
requests_.insert(req);
}
void remove(Request* req) // __attribute__ ((noinline))
{
muduo::MutexLockGuard lock(mutex_);
requests_.erase(req);
}
void printAll() const;
private:
mutable muduo::MutexLock mutex_;
std::set<Request*> requests_;
};
Inventory g_inventory;
class Request
{
public:
~Request()
{
x_ = -1;
muduo::MutexLockGuard lock(mutex_);
sleep(1);
g_inventory.remove(this);
}
void process() // __attribute__ ((noinline))
{
muduo::MutexLockGuard lock(mutex_);
printf("process() locked \n");
g_inventory.add(this);
// ...
}
void print() const __attribute__ ((noinline))
{
muduo::MutexLockGuard lock(mutex_);
// ...
printf("print Request %p x=%d\n", this, x_);
}
private:
mutable muduo::MutexLock mutex_;
int x_;
};
void Inventory::printAll() const
{
muduo::MutexLockGuard lock(mutex_);
printf("printAll() locked \n");
sleep(1);
for (std::set<Request*>::const_iterator it = requests_.begin();
it != requests_.end();
++it)
{
(*it)->print();
}
printf("Inventory::printAll unlocked\n");
}
void threadFunc()
{
Request *req = new Request;
req->process();
delete req;
}
int main()
{
muduo::Thread thread(threadFunc);
thread.start();
usleep(500 * 1000); // 为了让另一个线程等在前面第67行的sleep上
g_inventory.printAll();
thread.join();
}