互斥鎖試圖將想進入臨界區的所有線程都阻塞住,但是有時候該臨界區會涉及由這些線程共享的一個或多個數據的訪問或更新,這時候我們就需要用到讀寫鎖。
系統讀寫鎖的分配規則:
(1)只要有沒有線程持有給定的讀寫鎖用於寫,那麼任意數量的線程可以持有該讀寫鎖用於讀。(系統規定寫鎖優先,但是可以更改為讀鎖優先)
(2)僅當沒有線程持有某個讀寫鎖用於讀或用於寫時,才能分配該讀寫鎖用於寫。
讀寫鎖用於讀稱為共享鎖,讀寫鎖用於寫稱為獨占鎖。
讀寫鎖的獲取與釋放:
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
該函數獲取一個讀出鎖,如果對應的讀寫鎖已由某個寫入者持有,那就阻塞該調用線程。參數rwlock是讀寫鎖變量,類型為pthread_rwlock_t。
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
該函數嘗試獲取一個讀出鎖或寫入鎖,但是如果該所不能馬上獲得,就返回一個EBUSY錯誤,但是不阻塞調用線程。
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
該函數獲取一個寫入鎖,如果對應的讀寫鎖已由另一個寫入者持有,或者由一個或多個讀出者持有,就阻塞該調用線程。
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
該函數釋放一個讀出鎖或寫入鎖。
如果一個讀寫鎖是靜態分配的,即就是static pthread_rwlock_t rwlock,則其就初始化為PTHREAD_RWLOCK_INITIALIZER。
如果讀寫鎖是動態分配的,那就需要調用如下函數進行初始化和釋放!
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
讀寫鎖的動態銷毀函數。
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
讀寫鎖的動態初始化函數。第二個參數是屬性,可以設置為NULL即就是使用默認屬性,若不希望使用默認屬性則可以通過調用系統的屬性的初始化和銷毀函數來設置和銷毀屬性。
以下是一個使用讀寫鎖的實現讀優先的例子:
//頭文件
#pragma once
#include
#include
#include
#include
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;
}my_pthread_rwlock_t;
#define RW_MAGIC 0x19283746
#define MY_PTHREAD_RWLOCK_INITIALIZER {PTHREAD_MUTEX_INITIALIZER,\
PTHREAD_COND_INITIALIZER,\
PTHREAD_COND_INITIALIZER,\
RW_MAGIC, 0, 0, 0}
typedef int my_pthread_rwlockattr_t;
int my_pthread_rwlock_init(my_pthread_rwlock_t *, my_pthread_rwlockattr_t *);
int my_pthread_rwlock_rdlock(my_pthread_rwlock_t *);
int my_pthread_rwlock_tryrdlock(my_pthread_rwlock_t *);
int my_pthread_rwlock_wrlock(my_pthread_rwlock_t *);
int my_pthread_rwlock_trywrlock(my_pthread_rwlock_t *);
int my_pthread_rwlock_unlock(my_pthread_rwlock_t *);
int my_pthread_rwlock_destroy(my_pthread_rwlock_t *);
void clean_up(void *arg);
//讀寫鎖的初始化和銷毀函數
#include "pthread_rwlock.h"
//讀寫鎖的初始化
int my_pthread_rwlock_init(my_pthread_rwlock_t *rw, my_pthread_rwlockattr_t *attr)
{
if(attr != NULL){ //屬性只有設置了NULL,若不為NULL則返回錯誤
return(EINVAL);
}
int result;
if((result = pthread_mutex_init(&rw->rw_mutex, NULL)) != 0){ //互斥鎖的初始化,成功返回0,失敗返回非0
return result;
}
if((result = pthread_cond_init(&rw->rw_condreaders, NULL)) != 0){ //初始化讀條件變量,成功返回0,失敗時銷毀互斥量
pthread_mutex_destroy(&rw->rw_mutex);
}
if((result = pthread_cond_init(&rw->rw_condwriters, NULL)) != 0){ //初始化寫條件變量,成功返回0,失敗時銷毀互斥量和讀條件變量
pthread_cond_destroy(&rw->rw_condreaders);
pthread_mutex_destroy(&rw->rw_mutex);
}
rw->rw_nwaitreaders = 0; //初始化等待讀鎖的線程數
rw->rw_nwaitwriters = 0; //初始化等待寫鎖的線程數
rw->rw_refcount = 0; //初始化持有鎖的線程數
rw->rw_magic = RW_MAGIC; //初始化魔法數(魔法數可有可無,該數只是用來作判斷)
return 0;
}
//讀寫鎖的銷毀
int my_pthread_rwlock_destroy(my_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){ //若持有鎖的線程數不為0,或等待讀鎖的線程數不為0,或等待寫鎖的線程數不為0,就返回BUSY;
return (EBUSY);
}
pthread_mutex_destroy(&rw->rw_mutex); //銷毀互斥鎖
pthread_cond_destroy(&rw->rw_condreaders); //銷毀讀條件變量
pthread_cond_destroy(&rw->rw_condwriters); //銷毀寫條件變量
rw->rw_magic = 0; //魔法數賦0
return 0;
}
//系統默認的寫鎖優先實現
int my_pthread_rint my_pthread_rwlock_rdlock(my_pthread_rwlock_t *rw)
{
int result;
if(rw->rw_magic != RW_MAGIC){
return (EINVAL);
}
if((result = pthread_mutex_lock(&rw->rw_mutex)) != 0){ //mutex_lock 保護臨界區域
return result;
}
while(rw->rw_refcount < 0 || rw->rw_nwaitwriters > 0){ //(寫優先)有線程持有鎖,或是寫等待的線程數大於0,即有寫線程在等待鎖時,讀線程數增加
rw->rw_nwaitreaders++;
result = pthread_cond_wait(&rw->rw_condreaders, &rw->rw_mutex); //等待讀的線程被阻塞,當某時收到信號時,等待讀的線程被喚醒, 等待讀的線程數減少
rw->rw_nwaitreaders--;
if(result != 0){
break;
}
}
if(result == 0){
rw->rw_refcount++; //持有鎖的讀線程數增加,讀鎖是共享鎖,同時可有多個線程持有讀鎖
}
pthread_mutex_unlock(&rw->rw_mutex);
return result;
}
int my_pthread_rwlock_wrlock(my_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(result == 0){ //正常退出循環或者未進入循環
rw->rw_refcount = -1; //有一個線程持有寫鎖,寫鎖是獨占鎖,每次只能有一個線程持有寫鎖
}
pthread_mutex_unlock(&rw->rw_mutex);
return result;
}
#endif
int my_pthread_rwlock_unlock(my_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(rw->rw_refcount == -1){ //說明有一個線程持有寫鎖,解鎖完成後持有鎖的線程數為0,因為每次只會有一個線程持有寫鎖
rw->rw_refcount = 0;
}else{ //沒有線程持有鎖,解鎖失敗
printf("unlock error.\n");
}
//處理等待鎖的線程
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;
}wlock_rdlock(my_pthread_rwlock_t *rw)
{
int result;
if(rw->rw_magic != RW_MAGIC){
return (EINVAL);
}
if((result = pthread_mutex_lock(&rw->rw_mutex)) != 0){ //mutex_lock 保護臨界區域
return result;
}
while(rw->rw_refcount < 0 || rw->rw_nwaitwriters > 0){ //(寫優先)有線程持有鎖,或是寫等待的線程數大於0,即有寫線程在等待鎖時,讀線程數增加
rw->rw_nwaitreaders++;
result = pthread_cond_wait(&rw->rw_condreaders, &rw->rw_mutex); //等待讀的線程被阻塞,當某時收到信號時,等待讀的線程被喚醒, 等待讀的線程數減少
rw->rw_nwaitreaders--;
if(result != 0){
break;
}
}
if(result == 0){
rw->rw_refcount++; //持有鎖的讀線程數增加,讀鎖是共享鎖,同時可有多個線程持有讀鎖
}
pthread_mutex_unlock(&rw->rw_mutex);
return result;
}
int my_pthread_rwlock_wrlock(my_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(result == 0){ //正常退出循環或者未進入循環
rw->rw_refcount = -1; //有一個線程持有寫鎖,寫鎖是獨占鎖,每次只能有一個線程持有寫鎖
}
pthread_mutex_unlock(&rw->rw_mutex);
return result;
}
#endif
int my_pthread_rwlock_unlock(my_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(rw->rw_refcount == -1){ //說明有一個線程持有寫鎖,解鎖完成後持有鎖的線程數為0,因為每次只會有一個線程持有寫鎖
rw->rw_refcount = 0;
}else{ //沒有線程持有鎖,解鎖失敗
printf("unlock error.\n");
}
//處理等待鎖的線程
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;
}
//自己設置的讀鎖優先的實現
int my_pthread_rwlock_unlock(my_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){ //說明持有讀鎖的線程數 > 0
rw->rw_refcount--; //將讀鎖的數量減一
}else if(rw->rw_refcount == -1){ //說明有一個線程持有讀鎖
rw->rw_refcount = 0; //解鎖之後持有鎖的線程數為0
}else{ //沒有線程持有鎖,解鎖失敗
printf("unlock error.\n");
}
//處理等待加讀寫鎖的線程
if(rw->rw_nwaitreaders > 0){ //若等待加讀鎖的線程數>0,就廣播發送信號(讀鎖是共享鎖即每次可有多個線程加上讀鎖),使所有等待加讀鎖的線程全部加鎖
result = pthread_cond_broadcast(&rw->rw_condreaders);
}else if(rw->rw_nwaitwriters > 0){ //若等到加寫鎖的線程>0,並且持有鎖的線程的個數為0,就單播發送信號,使得等待加寫鎖的線程中的一個線程被喚醒
if(rw->rw_refcount == 0){
result = pthread_cond_signal(&rw->rw_condwriters);
}
}
pthread_mutex_unlock(&rw->rw_mutex); //解鎖互斥量
return result;
}
int my_pthread_rwlock_rdlock(my_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_nwaitreaders++; //所以等待加讀鎖的線程數增加
pthread_cleanup_push(clean_up, NULL);
result = pthread_cond_wait(&rw->rw_condreaders, &rw->rw_mutex); //等待加讀鎖的線程被阻塞,當某時收到加讀鎖信號的時候,等待加讀鎖的線程數減少
pthread_cleanup_pop(0);
rw->rw_nwaitreaders--;
if(result != 0){
break;
}
}
if(result == 0){ //正常退出循環或者未進入while循環
rw->rw_refcount++; //所有等待讀鎖的線程加鎖(讀鎖是共享鎖,同時可有多個線程加鎖成功)
}
pthread_mutex_unlock(&rw->rw_mutex); //解鎖互斥量
return result;
}
//讀鎖優先下的寫鎖實現
int my_pthread_rwlock_wrlock(my_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_nwaitreaders > 0){ //當持有鎖的線程數>0並且等待加讀鎖的線程數>0時,等待寫鎖的線程數加一
rw->rw_nwaitwriters++;
pthread_cleanup_push(clean_up, NULL);
result = pthread_cond_wait(&rw->rw_condwriters, &rw->rw_mutex); //等待加寫鎖的線程被阻塞,當某時收到可以加寫鎖的信號的時候,等待加寫鎖的一個線程被喚醒
pthread_cleanup_pop(0);
rw->rw_nwaitwriters--;
if(result != 0){
break;
}
}
if(result == 0){ //while循環正常退出或者未進入循環
rw->rw_refcount = -1; //一個線程持有寫鎖(寫鎖是獨占鎖,同一時刻只有一個線程持有寫鎖)
}
pthread_mutex_unlock(&rw->rw_mutex); //解鎖互斥量
return result;
}