如果已经有一个线程进入临界区,互斥锁会将所有试图访问临界区的其他线程阻塞。线程对临界区共享的数据有两种操作:读取和修改。互斥锁不区分这两种操作,而读写锁区分这两种操作。读写锁的规则如下:
1. 只要没有线程持有某个给定的读写锁用于写,那么任意数目的线程可以持有该读写锁用于读。
2. 仅当没有线程持有某个给定的读写锁用于读或写时,该读写锁才能用于写。
换一种说法是,只要没有线程在修改某个特定的数据,任意数目的线程都可以读取该数据。仅当没有线程在读取或修改该数据时,当前线程才能修改它。
读写锁有三种状态:
1. 未被任何线程占用
2. 作为写入锁(被一个线程占用用于写)
3. 作为读出锁(被一个或多个线程占用用于读)
在某些应用中,如果数据的读取比修改操作更频繁,那么可以使用读写锁来替代互斥锁提高性能。读写锁的数据类型是pthread_rwlock_t,如果读写锁变量是静态分配的,那么可以用PTHREAD_RWLOCK_INITIALIZER来初始化。
linux提供的读写锁API如下:
#include <pthread.h>
/*初始化读写锁*/
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
/*销毁读写锁*/
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
/*获取读写锁用于读*/
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
/*获取读写锁用于写*/
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
/*释放读写锁*/
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
/*读写锁属性初始化*/
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
/*销毁读写锁属性*/
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
/*获取读写锁进程共享属性*/
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *
restrict attr, int *restrict pshared);
/*设置读写锁进程共享属性*/
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr,
int pshared);
读写锁可以使用互斥锁和条件变量来实现,如下所示是我们自己实现的读写锁结构mypthread_rwlock_t。
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; /*读写锁状态*/
} mypthread_rwlock_t;
/*表示读写锁的魔数*/
#define RW_MAGIC 0x19283746
/*静态读写锁变量初始化宏*/
#define MYPTHREAD_RWLOCK_INITIALIZER {PTHREAD_MUTEX_INITIALIZER, \
PTHREAD_COND_INITIALIZER, PTHREAD_COND_INITIALIZER, \
RW_MAGIC, 0, 0, 0}
/*读写锁属性*/
typedef int mypthread_rwlockattr_t;
rw_magic是一个表示读写锁的魔数,值为0x19283746。rw_nwaitreaders为等待获得读出锁的线程个数。rw_nwaitwriters为等待获得写入锁的线程个数。rw_refcount表示读写锁的状态,为0表示该锁没有被占用,为-1表示作为写入锁,大于0表示作为读取锁且有多少个线程占有该读出锁。
我们自己实现的读写锁函数名字都是在原生函数名前面添加“my”前缀。
读写锁初始化和销毁函数实现如下:
int mypthread_rwlock_init(mypthread_rwlock_t *rw, mypthread_rwlockattr_t *attr)
{
int result;
if (attr != NULL)
return errno = 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_magic = RW_MAGIC;
return 0;
err3:
pthread_cond_destroy(&rw->rw_condreaders);
err2:
pthread_mutex_destroy(&rw->rw_mutex);
err1:
return result;
}
int mypthread_rwlock_destroy(mypthread_rwlock_t *rw)
{
if (rw->rw_magic != RW_MAGIC)
return errno = EINVAL;
if (rw->rw_refcount != 0 ||
rw->rw_nwaitreaders!= 0 || rw->rw_nwaitwriters!= 0)
return errno = 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;
}
尝试获取读写锁用于读数据(读出锁)的函数实现如下:
/*尝试获取读出锁*/
int mypthread_rwlock_tryrdlock(mypthread_rwlock_t *rw)
{
int result;
if (rw->rw_magic != RW_MAGIC)
return errno = EINVAL;
if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
return result;
/*如果当前读写锁是写入锁或者有线程在等待获取写入锁,则返回失败*/
if (rw->rw_refcount < 0 || rw->rw_nwaitwriters > 0)
result = errno = EBUSY;
else
rw->rw_refcount++;
pthread_mutex_unlock(&rw->rw_mutex);
return result;
}
获取读出锁的函数实现如下:
static void rwlock_cancelrdwait(void *arg)
{
mypthread_rwlock_t *rw;
rw = arg;
rw->rw_nwaitreaders--;
/*被取消时,由于占用了互斥锁,所以要解锁*/
pthread_mutex_unlock(&rw->rw_mutex);
}
/*获取读出锁*/
int mypthread_rwlock_rdlock(mypthread_rwlock_t *rw)
{
int result;
if (rw->rw_magic != RW_MAGIC)
return errno = EINVAL;
if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
return result;
/*当读写锁状态不是写入锁并且没有线程在等待获取写入锁时才能获取读出锁*/
while (rw->rw_refcount < 0 || rw->rw_nwaitwriters > 0) {
rw->rw_nwaitreaders++;
pthread_cleanup_push(rwlock_cancelrdwait, (void *)rw);
result = pthread_cond_wait(&rw->rw_condreaders, &rw->rw_mutex);
pthread_cleanup_pop(0);
rw->rw_nwaitreaders--;
if (result != 0)
break;
}
if (result == 0)
rw->rw_refcount++;
pthread_mutex_unlock(&rw->rw_mutex);
return result;
}
pthread_cleanup_push函数的作用是:如果调用mypthread_rwlock_rdlock的线程在阻塞于pthread_cond_wait函数期间被取消,它就不会从该函数返回,而是会调用函数rwlock_cancelrdwait。这个函数的作用是递减rw_nwaitreaders的值并释放线程占用的互斥锁。
pthread_cleanup_pop函数的作用是:如果线程从pthread_cond_wait函数返回,就删除这个清理函数(传入的参数为0)。
尝试获取读写锁用于写数据(写入锁)的函数实现如下:
int mypthread_rwlock_trywrlock(mypthread_rwlock_t *rw)
{
int result;
if (rw->rw_magic != RW_MAGIC)
return errno = EINVAL;
if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
return result;
/*如果当前锁被占用,则返回失败*/
if (rw->rw_refcount != 0)
result = errno = EBUSY;
else
rw->rw_refcount = -1;
pthread_mutex_unlock(&rw->rw_mutex);
return result;
}
获取写入锁的函数实现如下:
static void rwlock_cancelwrwait(void *arg)
{
mypthread_rwlock_t *rw;
rw = arg;
rw->rw_nwaitwriters--;
pthread_mutex_unlock(&rw->rw_mutex);
}
int mypthread_rwlock_wrlock(mypthread_rwlock_t *rw)
{
int result;
if (rw->rw_magic != RW_MAGIC)
return errno = EINVAL;
if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
return result;
/*当读写锁状态不是写入锁并且没有进程在等待获取读出锁时才能获取写入锁*/
while (rw->rw_refcount != 0) {
rw->rw_nwaitwriters++;
/**/
pthread_cleanup_push(rwlock_cancelwrwait, (void *)rw);
result = pthread_cond_wait(&rw->rw_condwriters, &rw->rw_mutex);
/**/
pthread_cleanup_pop(0);
rw->rw_nwaitwriters--;
if (result != 0)
break;
}
if (result == 0)
rw->rw_refcount = -1;
pthread_mutex_unlock(&rw->rw_mutex);
return result;
}
释放读写锁的函数实现如下:
int mypthread_rwlock_unlock(mypthread_rwlock_t *rw)
{
int result;
if (rw->rw_magic != RW_MAGIC)
return errno = EINVAL;
if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
return result;
/*如果当前读写锁是读出锁*/
if (rw->rw_refcount > 0)
rw->rw_refcount--;
/*如果当前读写锁是写入锁*/
else if (rw->rw_refcount == -1)
rw->rw_refcount = 0;
/*如果当前锁可用*/
else
err_dump("rw_refcount = %d", rw->rw_refcount);
/*如果有线程在等待获取写入锁,并且当前锁可用时,唤醒一个等待写入锁的进程*/
if (rw->rw_nwaitwriters> 0) {
if (rw->rw_refcount == 0)
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;
}