找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 5532|回复: 3

多线程lock心得

[复制链接]
发表于 2010-1-19 17:36:43 | 显示全部楼层 |阅读模式
Cpu硬件已经进入多核时代。多线程问题越来越难以回避,但是多线程是开发人员永远的噩梦。如何减轻多线程开发的痛苦,我总结下自己的一些经验心得,以求抛砖引玉,欢迎大家多提意见,互相学习进步。


一、        多线程开发准则:
1.        不要用多线程。
2.        非用不可时,明白其成本,极其昂贵。
3.        尽量少用多线程共享变量,多用栈内变量(小对象)和TLS(大对象)
4.        用好的 设计模式 去解决问题,而不是寄希望于梳理流程。
二、        锁的粒度(granularity)
1.        粗粒度。优点是安全,缺点是可能限制并发度,性能较差。鼓励使用。
2.        细粒度。优点是性能好,但是安全上有隐患。可以把细粒度看为一种性能优化。优化原则是:除非绝对必要,不要做优化。因此,不鼓励使用。
安全隐患在于:
1)        因为粒度较细,某些空隙可能会丢锁(该锁而未锁)。这个问题无解,是由多线程问题的复杂性本质决定的。
2)        较细的粒度,需要更多锁,这就增大了死锁的可能性。同样的原因,死锁问题也无解。
三、        安全性问题
1.        丢锁。没什么好说的,有可能多线程共享的资源就配一把锁。
2.        死锁。参考原则4,用好的 设计模式 去解决问题,而不是寄希望于梳理流程。
四、        一些比较好的设计模式
1.        Thread-Safe Interface
这个设计模式的特点是,以较小的成本,避免递归死锁。避免递归死锁常用recursive mutex,但是这种方法,每次调用都会上一次锁,成本较高。而Thread-Safe Interface只会锁一次。
具体思路:接口方法加锁,实现方法不加锁。如:
class A {
public:
bool find (int i) {
    Guard<LOCK> guard (lock_);
    return find_i(i);
}
void insert(int i) {
    Guard<LOCK> guard (lock_);
    insert_i(i);
}
private:
bool find_i(int i) {
    // search implementation here
}
void insert_i(int i) {
if find_i(i)  // must not be find(i)
   return;
// insertion implementation here
}
};
例中,接口方法只负责加锁,实现方法具体做事情。实现方法不可以再call interface method,只能call 相应的implementation method。这样锁会且只会被上一次,既安全又高效。
这个模式的正式定义和解释在POSA2(《Pattern-Oriented Software Architecture, Patterns for Concurrent and Networked Objects, Volume 2》),ACE中有很多地方用了此模式。

[ 本帖最后由 wishel 于 2010-1-21 18:18 编辑 ]
 楼主| 发表于 2010-1-21 17:54:39 | 显示全部楼层
2.        保持锁的顺序。
将可能同时申请的锁排序,每个锁分配一个序号,申请多个锁时,应该按顺序申请。这样可以避免交叉死锁。
这一模式可以配合Thread-Safe Interface一起使用。例如:
class A2;
class A1 {
public:
void f(A2& a2) {
    Guard<LOCK> guard (lock_);
    f_i(a2);
}
private:
void f_i(A2& a2) {
    // ……
    a2.g(); // right! a1's impl function call a2’s interface function
}
};
class A2 {
public:
void g() {
    Guard<LOCK> guard (lock_);
    g_i();
}
private:
void g_i() {
    // ……
}
};
class A3 {
public:
void h(A1& a1,A2& a2) {
    Guard<LOCK> guard (lock_);
    h_i(a1,a2);
}
private:
void h_i(A1& a1,A2& a2) {
    // ……
    a1.f(a2); // wrong! a3's impl function call a1’s interface function
}
};
总结:这个方式,简单清晰,而又不失效率。表面看起来似乎对设计有所限制,a1可以call a3,但a3不可以call a1。但是这是为了保证正确的锁顺序,如果a3确实有call a1的需要,即a1 < a3 && a3 < a1,则a1 == a3,可把a1和a3的共享资源合并,划归一个类管理。
发表于 2010-1-22 13:51:20 | 显示全部楼层
为什么呢?


可否介绍一下,你现在的项目产品都是1个线程在跑吗?
 楼主| 发表于 2010-1-22 15:16:54 | 显示全部楼层
不能说“都”,但是多数是1线程的。
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-11-21 20:37 , Processed in 0.015788 second(s), 5 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表