概述
一般大家对读写锁应该有一个认知,当读数据比修改数据频繁,我们可以采用读写锁。读写锁的分配规则如下:
- 只要没有线程持有某个给定的读写锁用于写时,那么任意数目的线程可以持有该读写锁用于读;
- 仅当没有线程持有某个给定的读写锁用于读或写时,才能分配该读写锁用于读。
获取与释放读写锁
读写锁的类型是pthread_rwlock_t,如果这个类型的某个变量是静态分配的,那么可以通过PTHREAD_RWLOCK_INITIALIZER来初始化它。
pthread_rwlock_rdlock获取一个读出锁,如果对应的读写锁已由某个写入者持有,那就阻塞调用线程。pthread_rwlock_wrlock获取一个写入锁,如果对应的读写锁已由另一个写入者持有,或者已由一个或多个读出者持有,那就阻塞调用线程。pthread_rwlock_unlock释放一个读出锁或写入锁。
pthread_rwlock_rdlock(pthread_rwlock_t *rwpt);
pthread_rwlock_wrlock(pthread_rwlock_t *rwpt);
pthread_rwlock_unlock(pthread_rwlock_t *rwpt);
下面两个函数尝试获取一个读出锁或写入锁,但是如果该锁不能马上取得,那就返回一个EBUSY错误,而不是把调用线程投入睡眠。
pthread_rwlock_tryrdlock(pthread_rwlock_t *rwpt);
pthread_rwlock_trywrlock(pthread_rwlock_t *rwpt);
读写锁属性
之前提到过,可通过一个静态分配的读写锁赋常值PTHREAD_RWLOCK_INITIALIZER来初始化它。读写锁变量也可以通过调用pthread_rwlock_init来动态地初始化它。当一个线程不再需要某个读写锁时,可以调用pthread_rwlock_destroy摧毁它。
pthread_rwlock_init(pthread_rwlock_t *rwpt, const pthread_rwlockattr_t *attr);
pthread_rwlock_destroy(pthread_rwlock_t *rwpt);
初始化某个读写锁时,如果attr是空指针,那就使用默认属性。要赋予它非默认的属性,需使用下面两个函数。
pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
数据类型为pthread_rwlockattr_t的某个属性对象一旦初始化,就通过调用不同的函数来启用或禁止特定属性。当前定义了唯一属性是PTHREAD_PROCESS_SHARED,它指定相应的读写锁将在不同进程间共享,而不仅仅是在单个进程内的不同线程间共享。以下两个函数分别获取和设置这个属性。
pthread_rwlockattr_getpshared(pthread_rwlockattr_t *attr, int *valptr);
pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int value);
第一个函数在由valptr指向的整数中返回该属性的当前值。第二个函数把该属性的当前值设置为value,其值或为PTHREAD_PROCESS_PRIVATE,或为PTHREAD_PROCESS_SHARED。
使用互斥锁和条件变量实现读写锁
只需要使用互斥锁和条件变量就能实现读写锁。下面将看到一种可能的实现,该实现优先考虑等待着的写入者。当然可以有其它方案。
首先给出pthread_rwlock.h的头文件,它定义了基本的pthread_rwlock_t的数据类型和操作读写锁的各个函数的函数原型,通常情况下它们是在<pthread.h>头文件中。
#ifndef __pthread_rwlock_h
#define __pthread_rwlock_h
typedef struct
{
pthread_mutex_t rw_mutex;
pthread_cond_t rw_condreaders;
pthread_cond_t rw_condwriters;
int rw_magic;
int rw_nwaitreaders;
int rw_nwaitwriters;
int rw_refcount;
}pthread_rwlock_t;
#define RW_MAGIC 0x19283746
#define PTHREAD_RWLOCK_INITIALIZER { PTHREAD_MUTEX_INITIALIZER,\
PTHREAD_COND_INITIALIZER, PTHREAD_COND_INITIALIZER,\
RW_MAGIC,0,0,0}
typedef int pthread_rwlockattr_t;
int pthread_rwlock_destroy(pthread_rwlock_t *);
int pthread_rwlock_init(pthread_rwlock_t *, pthread_rwlockattr_t* );
int pthread_rwlock_rdlock(pthread_rwlock_t *);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *);
int pthread_rwlock_trywrlock(pthread_rwlock_t *);
int pthread_rwlock_wrlock(pthread_rwlock_t *);
int pthread_rwlock_unlock(pthread_rwlock_t *);
#endif
pthread_rwlock_t数据类型含有一个互斥锁、两个条件变量、一个标志及三个计数器。无论何时检查或操作该结构,都必须持有其中的互斥锁成员rw_mutex。该结构初始化成功后,标志成员rw_magic就被设置成RW_MAGIC。所有函数都测试该成员,以检查调用者是否向某个已初始化的读写锁传递了指针。该读写锁被摧毁时,这个成员就被设置为0。
注意计数器成员之一rw_refcount总是指示着本读写锁的当前状态:-1表示它是一个写入锁,0表示它是可用的,大于0的值则意味着它当前容纳着那么多的读出锁。
下面给出phread_rwlock_int函数的实现,它动态地初始化一个读写锁。
int pthread_rwlock_init(pthread_rwlock_t *rw, pthread_rwlockattr_t *attr)
{
int result;
if (attr != NULL)
{
return (EINVAL);
}
if ((result = pthread_mutex_init(&rw->rw_mutex, NULL)) != 0)
goto err1;
if ((result = pthread_cond_init(&rw->rw_condreaders, NULL)) != 0)
goto err2;
if ((result = pthread_cond_init(&rw->rw_condwriters, NULL)) != 0)
goto err3;
rw->rw_nwaitreaders = 0;
rw->rw_nwaitwriters = 0;
rw->rw_refcount = 0;
rw->rw_magic = RW_MAGIC;
return 0;
err3:
pthread_cond_destroy(&rw->rw_condreaders);
err2:
pthread_mutex_destroy(&rw->rw_mutex);
err1:
return(result);
}
该函数不支持给读写锁赋属性,因此检查其attr是否为一个空指针。
初始化由调用者指定其指针的读写锁结构中的互斥锁和两个条件变量的成员。所有三个计数器成员都设置为0,rw_magic成员则设置为表示该结构已初始化完毕的值。
如果互斥锁或条件变量的初始化失败,那么小心地确保摧毁已初始化的对象,然后返回一个错误。
pthread_rwlock_destroy函数,当其所在的所有线程都不再持有也不试图持有某个读写锁的时候摧毁该锁。
int pthread_rwlock_destroy(pthread_rwlock_t *rw)
{
if (rw->rw_magic != RW_MAGIC)
return (EINVAL);
if ((rw->rw_refcount != 0)
|| (rw->rw_nwaitreaders != 0)
|| (rw->rw_nwaitwriters != 0))
{
return (EBUSY);
}
pthread_mutex_destroy(&rw->rw_mutex);
pthread_cond_destroy(&rw->rw_condreaders);
pthread_cond_destroy(&rw->rw_condwriters);
rw->rw_magic = 0;
return 0;
}
首先检查由调用者指定的读写锁已不在使用中,然后给其中的互斥锁和两个条件变量成员调用合适的摧毁函数。
pthread_rwlock_rdlock函数的实现如下所示:
int pthread_rwlock_rdlock(pthread_rwlock_t *rw)
{
int result;
if (rw->rw_magic != RW_MAGIC)
return EINVAL;
if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
return result;
while ((rw->rw_refcount < 0)
|| ( rw->rw_nwaitwriters > 0 ))
{
rw->rw_nwaitreaders++;
result = pthread_cond_wait(&rw->rw_condreaders, &rw->rw_mutex);
rw->rw_nwaitreaders--;
if (result != 0)
break;
}
if (0 == result)
rw->rw_refcount++;
pthread_mutex_unlock(&rw->rw_mutex);
return result;
}
无论何时操作pthread_rwlock_t类型的结构,都必须给其中的rw_mutex成员上锁。
如果rw_refcount小于0,意味着当前有一个写入者持有由调用者指定的读写锁,或者有线程正等着获取该读写锁的一个写入锁,rw_waitwriters大于0,那么我们无法获取该读写锁的一个读出锁。如果这两个条件有一个为真,那么我们就把rw_waitreaders加1。并在rw_condreaders条件变量上调用pthread_cond_wait。取得读出锁之后,把rw_refcount加1。互斥锁旋即释放。
pthread_rwlock_tryrdlock函数在尝试获取一个读出锁时并不阻塞,它的实现如下:
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rw)
{
if (rw->rw_magic != RW_MAGIC)
return EINVAL;
if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
return result;
if ((rw->rw_refcount < 0)
|| (rw->rw_nwaitwriters > 0))
return EBUSY;
else
rw->rw_refcount++;
pthread_mutex_unlock(&rw->rw_mutex);
return result;
}
如果当前有一个写入者持有调用者指定的读写锁,或者线程在等待该读写锁的一个写入锁,那么就返回EBUSY错误,否则,把rw_refcount加1获取该读写锁。
pthread_rwlock_wrlock函数获取一个写入锁,实现如下:
int pthread_rwlock_wrlock(pthread_rwlock_t *rw)
{
int result;
if (rw->rw_magic != RW_MAGIC)
return EINVAL;
if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
return result;
while (rw->rw_refcount != 0 )
{
rw->rw_nwaitwriters++;
result = pthread_cond_wait(&rw->rw_condwriters, &rw->rw_mutex);
rw->rw_nwaitwriters--;
if (result != 0)
break;
}
if (0 == result)
rw->rw_refcount = -1;
pthread_mutex_unlock(&rw->rw_mutex);
return result;
}
只要有读出者持有由调用者指定的读写锁的读出锁,或者有一个写入持有该读写锁的唯一写入锁,这两者情况rw_refcount都不为0,调用线程就得阻塞,为此,把rw_nwaitwriters加1,然后在rw_condwaitwriters条件变量上调用pthread_cond_wait,可以看到,向该条件变量发送信号的前提是,读写锁被释放并且有写入者在等待。
取得写入锁之后,把rw_refcount置为-1。
下面是pthread_rwlock_trywrlock函数的实现,该函数不是阻塞的。
int pthread_rwlock_trywrlock(pthread_rwlock_t *rw)
{
if (rw->rw_magic != RW_MAGIC)
return EINVAL;
if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
return result;
if ( rw->rw_refcount != 0 )
return EBUSY;
else
rw->rw_refcount = -1
pthread_mutex_unlock(&rw->rw_mutex);
return result;
}
该函数的实现和pthread_rwlock_tryrdlock很相似。
最后一个函数就是pthread_rwlock_unlock的实现:
int pthread_rwlock_unlock(pthread_rwlock_t *rw)
{
int result;
if (rw->rw_magic != RW_MAGIC)
return EINVAL;
if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
return result;
if (rw->rw_refcount > 0)
rw->rw_refcount--;
else if (-1 == rw->rw_refcount)
rw->rw_refcount = -1;
else
printf("rw->rw_refcount=%d\n", rw->rw_refcount);
if (rw->rw_nwaitwriters > 0)
{
if (0 == rw->rw_refcount)
result = pthread_cond_signal(&rw->rw_condwriters);
}
else if (rw->rw_nwaitreaders > 0)
{
result = pthread_cond_broadcast(&rw->rw_condreaders);
}
pthread_mutex_unlock(&rw->rw_mutex);
return result;
}
如果rw_refcount当前大于0,那么有一个读出者准备释放一个读出锁。如果rw_refcount为-1,那么有一个写入者准备释放一个写入锁。