1 获取与释放读写锁
读写锁的数据类型为pthread_rwlock_t。它可由PTHREAD_RWLOCK_INITIALIZER静态分配来初始化它。
pthread_rwlock_rdlock与pthread_rwlock_wrlock若获取不了读写锁,则会阻塞调用线程。
// 成功则返回0,走着返回正的Exxx值
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_unlock(pthread_rwlock_t *rwptr);
以下两个函数尝试获取一个读出锁或写入锁,若不能取得,则返回一个EBUSY错误,而不是阻塞调用线程。
// 成功则返回0,走着返回正的Exxx值
#include <pthread.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwptr);
2 读写锁属性
读写锁变量也可以通过调用pthread_rwlock_init动态初始化。当一个线程不再需要读写锁时,可以调用pthread_rwlock_destroy摧毁它。
// 成功则返回0,走着返回正的Exxx值
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *rwptr, const pthread_rwlockattr_t *attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwptr);
初始化某个读写锁时,若attr是空指针,那就使用默认属性。
要赋予它非默认的属性,需使用下面两个函数。
// 成功则返回0,走着返回正的Exxx值
#include <pthread.h>
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
数据类型为pthread_rwlockattr_t的某个属性对象一旦初始化,就定义了PTHREAD_PROCESS_SHARED,它指定相应的读写锁将在不同进程间共享。
以下两个函数分别获取和设置这两个属性:
// 成功则返回0,走着返回正的Exxx值
#include <pthread.h>
//由valptr指向的整数中返回该属性的当前值
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *valptr);
// value:PTHREAD_PROCESS_PRIVATE 或为 PTHREAD_PROCESS_SHARED
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int value);
3 使用互斥锁和条件变量实现读写锁
只需使用互斥锁和条件变量就能实现读写锁。
3.1 pthread_rwlock_t数据类型
- 无论何时检查或操作该结构,都必须持有其中的互斥锁成员;
- 该结构初始化成功后,标志成员rw_magic就被设置成RW_MAGIC。所有函数都测试该成员,以检查调用者是否向某个已初始化的读写锁传递了指针。该读写锁被摧毁时,这个成员设置为0.
#ifndef _pthread_rwlock_h
#define _pthread_rwlock_h
typedef struct
{
rw_mutex
pthread_mutex_t rw_mutex; // basic lock in this struct
pthread_cond_t rw_condreaders; // for reader threads waiting
pthread_cond_t rw_condwriters; // for writer threads waiting
int rw_magic; // for error checking
int rw_nwaitreaders; // the number waiting of readers
int rw_nwaitwriters; // the number waiting of writers
int rw_refcount; // -1 if writer has the lock, else #readers holding the lock
} pthread_rwlock_t;
#define RW_MAGIC 0x19283746
// following must have same ordeer as elements in struct above
#define PTHREAD_RWLOCK_INITIALIZER { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER,
PTHREAD_MUTEX_INITIALIZER, RW_MAGIC, 0, 0, 0 }
typedef int pthread_rwlockattr_; // dummy
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 *);
// and our wrapper func
void Pthread_rwlock_destroy(pthread_rwlock_t);
void Pthread_rwlock_init(pthread_rwlock_t *, pthread_rwlockattr_t t*);
void Pthread_rwlock_rdlock(pthread_rwlock_t *);
int Pthread_rwlock_tryrdlock(pthread_rwlock_t *);
int Pthread_rwlock_trywrlock(pthread_rwlock_t *);
void Pthread_rwlock_unlock(pthread_rwlock_t *);
void Pthread_rwlock_wrlock(pthread_rwlock_t *);
#endif // __pthread_rwlock_h
3.1 pthread_rwlock_init函数
#include "unpipc.h"
#include "pthread_rwlock.h"
int pthread_rwlock_init(pthread_rwlock_t *rw, pthread_rwlockattr_t *attr)
{
int result;
if (attr != NULL)
return(EINVAL); // not supported
// 初始化互斥锁和两个条件变量成员
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;
// 所有3个计数器成员都设置为0,rw_magic设置为已初始化完毕的对象
rw->rw_nwaitreaders = 0;
rw->rw_nwaitwriter = 0;
rw->rw_refcout = 0;
rw->rw_magic = RW_MAGIC;
return(0);
err3:
pthread_cond_destroy(&rw->rw_condreaders);
err2:
pthread_mutex_destroy(&rw->rw_mutex);
err3:
return(result) // an errno value
}
3.2 pthread_rwlock_destroy函数
- 它在所有线程(包括调用者在内)都不再持有也不试图持有某个读写锁的时候摧毁该锁。
#include "unpipc.h"
#include "pthread_rwlock.h"
int pthread_rwlock_destroy(pthread_rwlock_t *rw)
{
if (rw->rw_magic != RW_MAGIC)
return(EINVAL);
if (rw->rw_refcout != 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);
}
3.3 pthread_rwlock_rdlock函数
- 若(a)当前有一个写入者持有由调用者指定的读写锁 或 (b)有线程正等着获取该读写锁的写入锁则无法获取该读写锁的一个读出锁。写等待者计数器加1,并在rw_condreaders条件变量上调用pthread_cond_wait。
#include "unpipc.h"
#include "pthread_rwlock.h"
int pthread_rwlock_rdlock(pthread_rwlock_t *rw)
{
int result;
if (rw->rw_magic != RW_MAGIC)
return(EINVAL);
// 无论何时操作pthread_rwlock_t类型的结构,都必须给其中的rw_mutex成员上锁
if ( (result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
return(result);
// give preference to waiting writers
// 若(a)当前有一个写入者持有由调用者指定的读写锁 或 (b)有线程正等着获取该读写锁的写入锁
// 则无法获取该读写锁的一个读出锁。写等待者计数器加1,并在rw_condreaders条件变量上调用
// pthread_cond_wait。
while (rw->rw_refcout < 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;
}
// 取得读出锁后把rw_refcuont加1。互斥锁释放
if (result == 0)
rw->rw_refcount++; // another reader has a read lock;
pthread_mutex_unlock(&rw->rw_mutex);
return (result);
}
3.4 pthread_rwlock_tryrdlock函数
- 若当前有一个写入者持有调用者指定的读写锁,或者有线程在等待读写锁的一个写入锁,则返回EBUSY错误。否则,rw_refcount+1并获取该读写锁。
#include "unpipc.h"
#include "pthread_rwlock.h"
int pthread_rwlock_tryrdlock(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_refcout < 0 || rw->rw_nwaitwriters > 0)
result = EBUSY; // held by a writer or waiting writers
else
rw->rw_refcount++;
pthread_mutex_unlock(&rw->rw_mutex);
return (result);
}
3.4 pthread_rwlock_wrlock函数
- 若有读出者持有调用者指定的读写锁的读出锁,或者有一个写入者持有该读写锁的唯一写入锁,线程阻塞。在rw_condwriters条件变量上调用pthread_cond_wait。 我们将看到,向该条件变量发送信号的前提是:它所在的读写锁被释放,并且有写入者正在等待。
#include "unpipc.h"
#include "pthread_rwlock.h"
int pthread_rwlock_rdlock(pthread_rwlock_t *rw)
{
int result;
if (rw->rw_magic != RW_MAGIC)
return(EINVAL);
// 无论何时操作pthread_rwlock_t类型的结构,都必须给其中的rw_mutex成员上锁
if ( (result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
return(result);
while (rw->rw_refcout != 0) {
rw->rw_nwaitwriters++;
result = pthread_cond_wait(&rw->rw_condwriters, &rw->rw_mutex);
rw->rw_nwaitwriters--;
if (result != 0)
break;
}
if (result == 0)
rw->rw_refcount = -1; // another reader has a read lock;
pthread_mutex_unlock(&rw->rw_mutex);
return (result);
}
3.5 pthread_rwlock_unlock函数
- 若有一个写入者在等待,则一旦调用者指定的读写锁可用时,就向rw_condwriters条件变量发送信号。使pthread_cond_signal来唤醒一个线程。若无写入者在等待,但有一个或多个读出者等待,则在rw_condreaders条件变量上调用pthread_cond_broadcast,因为所有等待着的读出者都可以获取一个读出锁。
#include "unpipc.h"
#include "pthread_rwlock.h"
int pthread_rwlock_unlock(pthread_rwlock_t *rw)
{
int result;
if (rw->rw_magic != RW_MAGIC)
return(EINVAL);
// 无论何时操作pthread_rwlock_t类型的结构,都必须给其中的rw_mutex成员上锁
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);
// 若有一个写入者在等待,则一旦调用者指定的读写锁可用时,就向rw_condwriters条件变量发送信号。
// 使用pthread_cond_signal来唤醒一个线程。若无写入者在等待,但有一个或多个读出者等待,则在
// rw_condreaders条件变量上调用pthread_cond_broadcast,因为所有等待着的读出者都可以获取
// 一个读出锁
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);
}
4 线程取消
-
若pthread_rwlock_rdlock的调用线程阻塞在其中的pthread_cond_wait调用上并随后被取消,它就在仍持有互斥锁的情况下终止。通过由对方调用函数pthread_cancel,一个线程可以被同一进程内的任何其他线程所取消。
#include <pthread.h> // 成功返回0,否则返回EXXX值,参数为线程ID int pthread_cancel(pthread_t id);
-
举例:①若启动了多个线程以执行某个给定任务(如在某个数据库中查找一个记录),则首先完成任务的线程可使用线程取消功能取消其他线程。②当多个线程开始执行同一个任务时,若其中某个线程发现一个错误,它和其他线程就有必要终止。
5 小结
- 与普通的互斥相比,当被保护数据的读访问比写访问更频繁时,读写锁能提供更高的并发度。
- 读写锁可以只通过使用互斥锁和条件变量来实现。本章的代码是优先考虑着的写入者。