现在的位置: 首页 > 综合 > 正文

系统程序员成长计划-并发(四)(下)

2013年12月11日 ⁄ 综合 ⁄ 共 2203字 ⁄ 字号 评论关闭

读写锁

读写锁在加锁时,要区分是为了读而加锁还是为了写而加锁,所以和递归锁不同的是,它无法兼容Locker接口了。不过为了做到不依赖于特定平台,我
们可以利用Locker的接口来抽象锁的实现。利用现有的锁来实现读写锁。读写锁的可变的部分已经被Locker隔离了,所以读写锁本身不需要做成接口。
它只是一个普通对象而已:

struct _RwLocker;
typedef struct _RwLocker RwLocker;

RwLocker* rw_locker_create(Locker* rw_locker, Locker* rd_locker);

Ret rw_locker_wrlock(RwLocker* thiz);
Ret rw_locker_rdlock(RwLocker* thiz);
Ret rw_locker_unlock(RwLocker* thiz);

void rw_locker_destroy(RwLocker* thiz);

o 创建读写锁

RwLocker* rw_locker_create(Locker* rw_locker, Locker* rd_locker)
{
RwLocker* thiz = NULL;
return_val_if_fail(rw_locker != NULL && rd_locker != NULL, NULL);

thiz = (RwLocker*)malloc(sizeof(RwLocker));
if(thiz != NULL)
{
thiz->readers = 0;
thiz->mode = RW_LOCKER_NONE;
thiz->rw_locker = rw_locker;
thiz->rd_locker = rd_locker;
}

return thiz;
}

读写锁的基本要求是:写的时候不允许任何其它线程读或者写,读的时候允许其它线程读,但不允许其它线程写。所以在实现时,写的时候一定要加锁,第一
个读的线程要加锁,后面其它线程读时,只是增加锁的引用计数。我们需要两个锁:一个锁用来保存被保护的对象,一个锁用来保护引用计数。

o 加写锁

Ret rw_locker_wrlock(RwLocker* thiz)
{
Ret ret = RET_OK;
return_val_if_fail(thiz != NULL, RET_INVALID_PARAMS);

if((ret = locker_lock(thiz->rw_locker)) == RET_OK)
{
thiz->mode = RW_LOCKER_WR;
}

return ret;
}

加写锁很简单,直接加保护受保护对象的锁,然后修改锁的状态为已加写锁。后面其它的线程想写,就会这个锁上等待,如果想读也要等待(见后面)。

o 加读锁

Ret rw_locker_rdlock(RwLocker* thiz)
{
Ret ret = RET_OK;
return_val_if_fail(thiz != NULL, RET_INVALID_PARAMS);

if((ret = locker_lock(thiz->rd_locker)) == RET_OK)
{
thiz->readers++;
if(thiz->readers == 1)
{
ret = locker_lock(thiz->rw_locker);
thiz->mode = RW_LOCKER_RD;
}
locker_unlock(thiz->rd_locker);
}

return ret;
}

先尝试加保护引用计数的锁,增加引用计数。如果当前线程是第一个读,就要去加保护受保护对象的锁。如果此时已经有线程在写,就等待直到加锁成功,然后把锁的状态设置为已加读锁,最后解开保护引用计数的锁。

o 解锁

Ret rw_locker_unlock(RwLocker* thiz)
{
Ret ret = RET_OK;
return_val_if_fail(thiz != NULL, RET_INVALID_PARAMS);

if(thiz->mode == RW_LOCKER_WR)
{
thiz->mode == RW_LOCKER_NONE;
ret = locker_unlock(thiz->rw_locker);
}
else
{
assert(thiz->mode == RW_LOCKER_RD);
if((ret = locker_lock(thiz->rd_locker)) == RET_OK)
{
thiz->readers--;
if(thiz->readers == 0)
{
thiz->mode == RW_LOCKER_NONE;
ret = locker_unlock(thiz->rw_locker);
}
locker_unlock(thiz->rd_locker);
}
}

return ret;
}

解锁时根据状态来决定,解写读直接解保护受保护对象的锁。解读锁时,先要加锁保护引用计数的锁,引用计数减一。如果自己是最后一个读,才解保护受保护对象的锁,最后解开保护引用计数的锁。

从上面读写锁的实现,我们可以看出,读写锁要充分发挥作用,就要基于两个假设:

o 读写的不对称性,读的次数远远大于写的次数。像数据库就是这样,决大部分时间是在查询,而修改的情况相对少得多,所以数据库通常使用读写锁。

o 处于临界区的时间比较长。从上面的实现来看,读写锁实际上比正常加/解锁的次数反而要多,如果处于临界区的时间比较短,比如和修改引用计数差不多,使用读写锁,即使全部是读,它的效率也会低于正常锁。

本节示例请到这里下载。

抱歉!评论已关闭.