之前我們整理了互斥鎖與條件變量問題它保證了共享資源的安全,但在多線程中我們也會經常對共享數據進行讀、寫操作。也就是說對某些資源的訪問會 存在兩種可能的情況,一種是訪問必須是排查性的,就是獨占的意思,這稱作寫操作;另一種情況就是訪問方式可以是共享的,就是說可以有多個線程同時去訪問某個資源,這種就稱作讀操作。這個問題模型是從對文件的讀寫操作中引申出來的。
讀寫鎖比起mutex具有更高的適用性,具有更高的並行性,可以有多個線程同時占用讀模式的讀寫鎖,但是只能有一個線程占用寫模式的讀寫鎖,讀寫鎖的三種狀態:
1.當讀寫鎖是寫加鎖狀態時,在這個鎖被解鎖之前,所有試圖對這個鎖加鎖的線程都會被阻塞
2.當讀寫鎖在讀加鎖狀態時,所有試圖以讀模式對它進行加鎖的線程都可以得到訪問權,但是以寫模式對它進行加鎖的線程將會被阻塞
3.當讀寫鎖在讀模式的鎖狀態時,如果有另外的線程試圖以寫模式加鎖,讀寫鎖通常會阻塞隨後的讀模式鎖的請求,這樣可以避免讀模式鎖長期占用,而等待的寫模式鎖請求則長期阻塞。
讀寫鎖最適用於對數據結構的讀操作次數多於寫操作的場合,因為,讀模式鎖定時可以共享,而寫模式鎖定時只能某個線程獨占資源,因而,讀寫鎖也可以叫做個共享-獨占鎖。
處理讀-寫問題的兩種常見策略是強讀者同步和強寫者同步
強讀者同步中,總是給讀者更高的優先權,只要寫者當前沒有進行寫操作,讀者就可以獲得訪問權限;而在強寫者同步中,則往往將優先權交付給寫者,而讀者只能等到所有正在等待的或者是正在執行的寫者結束以後才能執行。系統中的讀寫鎖時用的強寫者,這是為了避免再修改數據時,讀操作先執行導致讀出的數據無效。
下面分別是強讀者和強寫者的函數實現代碼
強寫者實現代碼::
pthread_rwlock.h:
#pragma once //只編譯一次
#include
#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_nwaitwriters; //定義一個讀等待標記等待讀的線程數
int rw_nwaitreaders; //定義一個寫等待標記等待寫的線程數
int rw_refcount; //標記現在所使用的個數,只能為 -1(代表有線程進行寫操作)、0(沒有線程對共享數據進行讀寫操作)、
//>0(代表現在進行讀操作的線程數,因為可以同時多個線程進行讀操作,所以可以一個大於0的整數)。
}my_pthread_rwlock_t;
#define RW_MAGIC 0x19283746 //初始魔數值
//初始化my_pthread_rwlock_t。
#define
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 *rw); //讀鎖函數
int my_pthread_rwlock_wrlock(my_pthread_rwlock_t *rw); //寫鎖函數
int my_pthread_rwlock_tryrdlock(my_pthread_rwlock_t *rw); //嘗試上讀鎖
int my_pthread_rwlock_trywrlock(my_pthread_rwlock_t *rw); //嘗試上寫鎖
int my_pthread_rwlock_unlock(my_pthread_rwlock_t *rw); //解鎖函數
int my_pthread_rwlock_destroy(my_pthread_rwlock_t *rw); //銷毀讀寫鎖
pthread_rwlock.cpp:
#include "pthread_rwlock.h"
//讀寫鎖初始化函數
int my_pthread_rwlock_init(my_pthread_rwlock_t *rw, my_pthread_rwlockattr_t *attr)
{
int res;
if(attr != NULL){ //如果創建屬性不為NULL,就返回不可用
return(EINVAL);
}
if(res = pthread_mutex_init(&rw->rw_mutex, NULL) != 0){ //初始化互斥鎖,如果成功則返回0,否則初始化互斥鎖失敗
goto err1; //初始化失敗調轉到err1處理情況
}
if(res = pthread_cond_init(&rw->rw_condreaders, NULL) != 0){ //初始化讀條件變量,如果成功則返回0,否則初始化讀條件變量失敗
goto err2; //初始化失敗後調轉到err2處理方法
}
if(res = pthread_cond_init(&rw->rw_condwriters, NULL) != 0){ //初始化寫條件變量,如果成功返回0,
goto err3; //初始化失敗後跳轉到err3處理方法。
}
rw->rw_nwaitreaders = 0; //初始化等待讀操作的線程數為0
rw->rw_nwaitwriters = 0; //初始化等待寫操作的線程數為0
rw->rw_refcount = 0; //初始化正在操作共享數據的線程數為0
rw->rw_magic = RW_MAGIC; //將初始值賦給檢驗變量魔數
return 0;
err3: //因為執行err3是創建了讀條件變量和互斥鎖,鎖以銷毀讀條件變量,然後順序執行進入err2銷毀互斥量,
//最後順序執行err1,返回錯誤參數。這就是如此設計的原因。
pthread_cond_destroy(&rw->rw_condreaders);
err2: //如果是直接跳入err2的,那此時就只有互斥量了,直接銷毀它然後返回錯誤參數。
pthread_mutex_destroy(&rw->rw_mutex);
err1: //此時還沒有創建成功任何變量或所創建變量已在err3和err2中銷毀,直接返回錯誤參數。
return res;
}
//銷毀讀寫鎖函數
int my_pthread_rwlock_destroy(my_pthread_rwlock_t *rw)
{
if(rw->rw_magic != RW_MAGIC){ //如果rw_magic不等於初始值,說明有線程在使用,直接返回
return EINVAL;
}
//如果有線程等待執行讀操作或有線程在等待執行寫操作,則返回EBUSY錯誤,並結束函數
if(rw->rw_nwaitreaders != 0 || rw->rw_nwaitwriters != 0 || rw->rw_refcount != 0){
return EBUSY;
}
//如果rw_magic為初始值,且沒有線程再等待讀操作或寫操作時,將互斥量、讀條件變量、寫條件變量分別銷毀
pthread_mutex_destroy(&rw->rw_mutex);
pthread_cond_destroy(&rw->rw_condreaders);
pthread_cond_destroy(&rw->rw_condwriters);
rw->rw_magic = 0; //最後將rw_magic置為0
return 0;
}
//讀鎖的實現函數
int my_pthread_rwlock_rdlock(my_pthread_rwlock_t *rw)
{
int res;
if(rw->rw_magic != RW_MAGIC){ //如果rw_magic不為初始值說明有線程正在進行操作,直接返回EINVAL錯誤並結束函數
return EINVAL;
}
//無論何時操作my_pthread_rwlock結構體都要對rw_mutex成員上鎖
if((res = pthread_mutex_lock(&rw->rw_mutex)) != 0){
return res;
}
//查看rw_refcount的值看是否小於0(即是否有線程對其進行寫操作),或是否有線程正在等待對其進行寫操作,
//如果有則將rw_nwaitreaders值加1,並將程序阻塞等待讀條件變量後在對其上所進行讀操作
//(因為此程序實現的是寫優先,所以要等到沒有寫操作線程或等待寫操作的線程時,才能讓進行讀的線程獲得資源)
while(rw->rw_refcount < 0 || rw->rw_nwaitwriters > 0){
rw->rw_nwaitreaders++;
res = pthread_cond_wait(&rw->rw_condreaders, &rw->rw_mutex);
if(res != 0){
break;
}
}
//如果讀鎖加成功後,給refcount加1,因為正數值代表的時進行讀操作的線程個數
if(res == 0){
rw->rw_refcount++;
}
//對控制my_pthread_rwlock結構體操作的rw_mutex成員解鎖,
//保證讓其他線程可以操作my_pthread_rwlock結構體
pthread_mutex_unlock(&rw->rw_mutex);
return res;
}
//寫操作上鎖函數
int my_pthread_rwlock_wrlock(my_pthread_rwlock_t *rw)
{
int res;
if(rw->rw_magic != RW_MAGIC){ //如果rw_magic不為初始值說明有線程正在進行操作,直接返回EINVAL錯誤並結束函數
return EINVAL;
}
//無論何時操作my_pthread_rwlock結構體都要對rw_mutex成員上鎖
if(res = pthread_mutex_lock(&rw->rw_mutex) != 0){
return res;
}
//查看rw_refcount的值看是否為0(即是否有線程對其進行操作),如果rw_refcount不為0,則將rw_nwaitwriters值加1,
//並將程序阻塞等待接收到寫條件變量後再對其上所進行讀操作(因為此程序實現的是寫優先,所以只要沒有線程操作時,就讓該線程進行寫操作)
while(rw->rw_refcount != 0){
rw->rw_nwaitwriters ++;
res = pthread_cond_wait(&rw->rw_condwriters, &rw->rw_mutex);
rw->rw_nwaitwriters--;
if(res != 0){
break;
}
}
//如果讀鎖加成功後,給refcount置為-1,因為-1代表寫操作(因為只能同時有且僅有一個線程進行寫操作,所以,加讀鎖後rw_refcount只能為-1)
if(res == 0){
rw->rw_refcount = -1;
}
//對控制my_pthread_rwlock結構體操作的rw_mutex成員解鎖,保證讓其他線程可以操作my_pthread_rwlock結構體
pthread_mutex_unlock(&rw->rw_mutex);
return res;
}
int my_pthread_rwlock_tryrdlock(my_pthread_rwlock_t *rw)
{
int res;
if(rw->rw_magic != RW_MAGIC){ //如果rw_magic不為初始值說明有線程正在進行操作,直接返回EINVAL錯誤並結束函數
return EINVAL;
}
//無論何時操作my_pthread_rwlock結構體都要對rw_mutex成員上鎖
if((res = pthread_mutex_lock(&rw->rw_mutex)) != 0){
return res;
}
//此處是嘗試上所函數中讀寫優先的不同地方。
//如果有寫線程正在執行或有寫線程在等待,則返回EBUSY
if(rw->rw_refcount < 0 || rw->nwaitwriters > 0){
res = EBUSY;
}else{ //否則,讓rw_refcount加1,讓其准確的記錄當前正在執行的讀進程個數
rw->rw_refcount ++;
}
//對控制my_pthread_rwlock結構體操作的rw_mutex成員解鎖,
//保證讓其他線程可以操作my_pthread_rwlock結構體
pthread_mutex_unclock(&rw->rw_mutex);
return res;
}
int my_pthread_rwlock_trywrlock(my_pthread_rwlock_t *rw)
{
int res;
if(rw->rw_magic != RW_MAGIC){
//如果rw_magic不為初始值說明有線程正在進行操作,直接返回EINVAL錯誤並結束函數
return EINVAL;
}
//無論何時操作my_pthread_rwlock結構體都要對rw_mutex成員上鎖
if((res = pthread_mutex_lock(&rw->rw_mutex)) != 0){
return res;
}
//如果正在操作的線程數不為0,則返回EBUSY
if(rw->rw_refcount != 0){
res = EBUSY;
}else{ //否則,讓rw_refcount = -1,讓其狀態為有寫進程操作
rw->rw_refcount = -1;
}
//對控制my_pthread_rwlock結構體操作的rw_mutex成員解鎖,
//保證讓其他線程可以操作my_pthread_rwlock結構體
pthread_mutex_unclock(&rw->rw_mutex);
return res;
}
//解鎖函數
int my_pthread_rwlock_unlock(my_pthread_rwlock_t *rw)
{
int res;
if(rw->rw_magic != RW_MAGIC){
//如果rw_magic不為初始值說明有線程正在進行操作,直接返回EINVAL錯誤並結束函數
return EINVAL;
}
//無論何時操作my_pthread_rwlock結構體都要對rw_mutex成員上鎖
if((res = pthread_mutex_lock(&rw->rw_mutex)) != 0){
return res;
}
//如果此時rw_refcount大於0,所以現在再執行的操作為讀操作,給rw_refcount減1,解除讀線程的鎖
if(rw->rw_refcount > 0){
rw->rw_refcount--;
}else if(rw->rw_refcount == -1){ //如果rw_refcount = -1,說明執行的是寫操作,將其值賦為0,解除寫線程的鎖
rw->rw_refcount = 0;
}else{
rw->rw_refcount = 0;
}
//此處是寫著優先和讀者優先實現的第二處不同
if(rw->rw_nwaitwriters > 0){ //當等待寫操作的線程數不為0
if(rw->rw_refcount == 0){ //rw_refcount正在執行的線程數為0
//發送寫條件變量,通知寫線程有空閒資源
res = pthread_cond_signal(&rw->rw_condwriters);
}
}else if(rw->rw_nwaitreaders > 0){
//再沒有寫線程等待操作且有可用資源的前提下,廣播發送讀條件變量,通知所有等待讀操作的線程有空閒資源
res = pthread_cond_broadcast(&rw->rw_condreaders);
}
//對控制my_pthread_rwlock結構體操作的rw_mutex成員解鎖,
//保證讓其他線程可以操作my_pthread_rwlock結構體
pthread_mutex_unlock(&rw->rw_mutex);
return res;
}
讀者優先程序代碼:
pthread_rwlock.cpp:
#include "pthread_rwlock.h"
//讀寫鎖初始化函數
int my_pthread_rwlock_init(my_pthread_rwlock_t *rw, my_pthread_rwlockattr_t *attr)
{
int res;
if(attr != NULL){ //如果創建屬性不為NULL,就返回不可用
return(EINVAL);
}
if(res = pthread_mutex_init(&rw->rw_mutex, NULL) != 0){ /初始化互斥鎖,如果成功則返回0,否則初始化互斥鎖失敗
goto err1; //初始化失敗調轉到err1處理情況
}
if(res = pthread_cond_init(&rw->rw_condreaders, NULL) != 0){ //初始化讀條件變量,如果成功則返回0,否則初始化讀條件變量失敗
goto err2; //初始化失敗後調轉到err2處理方法
}
if(res = pthread_cond_init(&rw->rw_condwriters, NULL) != 0){ //初始化寫條件變量,如果成功返回0,
goto err3; //初始化失敗後跳轉到err3處理方法。
}
rw->rw_nwaitreaders = 0; //初始化等待讀操作的線程數為0
rw->rw_nwaitwriters = 0; //初始化等待寫操作的線程數為0
rw->rw_refcount = 0; //初始化正在操作共享數據的線程數為0
rw->rw_magic = RW_MAGIC; //將初始值賦給檢驗變量魔數
return 0;
err3: //因為執行err3是創建了讀條件變量和互斥鎖,鎖以銷毀讀條件變量,然後順序執行進入err2銷毀互斥量,
//最後順序執行err1,返回錯誤參數。這就是如此設計的原因。
pthread_cond_destroy(&rw->rw_condreaders);
err2: //如果是直接跳入err2的,那此時就只有互斥量了,直接銷毀它然後返回錯誤參數。
pthread_mutex_destroy(&rw->rw_mutex);
err1: //此時還沒有創建成功任何變量或所創建變量已在err3和err2中銷毀,直接返回錯誤參數。
return res;
}
//銷毀讀寫鎖函數
int my_pthread_rwlock_destroy(my_pthread_rwlock_t *rw)
{
if(rw->rw_magic != RW_MAGIC){ //如果rw_magic不等於初始值,說明有線程在使用,直接返回
return EINVAL;
}
//如果有線程等待執行讀操作或有線程在等待執行寫操作,則返回EBUSY錯誤,並結束函數
if(rw->rw_nwaitreaders != 0 || rw->rw_nwaitwriters != 0 || rw->rw_refcount != 0){
return EBUSY;
}
//如果rw_magic為初始值,且沒有線程再等待讀操作或寫操作時,將互斥量、讀條件變量、寫條件變量分別銷毀
pthread_mutex_destroy(&rw->rw_mutex);
pthread_cond_destroy(&rw->rw_condreaders);
pthread_cond_destroy(&rw->rw_condwriters);
rw->rw_magic = 0; //最後將rw_magic置為0
return 0;
}
//讀鎖的實現函數
int my_pthread_rwlock_rdlock(my_pthread_rwlock_t *rw)
{
int res;
if(rw->rw_magic != RW_MAGIC){ //如果rw_magic不為初始值說明有線程正在進行操作,直接返回EINVAL錯誤並結束函數
return EINVAL;
}
//無論何時操作my_pthread_rwlock結構體都要對rw_mutex成員上鎖
if((res = pthread_mutex_lock(&rw->rw_mutex)) != 0){
return res;
}
//查看rw_refcount的值看是否小於0(即是否有線程對其進行寫操作),如果有寫線程正在執行,則將rw_nwaitreaders值加1,
//並將程序阻塞等待讀條件變量後在對其上所進行讀操作
//(因為此程序實現的是讀優先,所以只要沒有寫線程正在執行,就讓進行讀的線程獲得資源)
while(rw->rw_refcount < 0){
rw->rw_nwaitreaders++;
res = pthread_cond_wait(&rw->rw_condreaders, &rw->rw_mutex);
if(res != 0){
break;
}
}
//如果讀鎖加成功後,給refcount加1,因為正數值代表的時進行讀操作的線程個數
if(res == 0){
rw->rw_refcount++;
}
//對控制my_pthread_rwlock結構體操作的rw_mutex成員解鎖,保證讓其他線程可以操作my_pthread_rwlock結構體
pthread_mutex_unlock(&rw->rw_mutex);
return res;
}
//寫操作上鎖函數
int my_pthread_rwlock_wrlock(my_pthread_rwlock_t *rw)
{
int res;
if(rw->rw_magic != RW_MAGIC){
//如果rw_magic不為初始值說明有線程正在進行操作,直接返回EINVAL錯誤並結束函數
return EINVAL;
}
//無論何時操作my_pthread_rwlock結構體都要對rw_mutex成員上鎖
if(res = pthread_mutex_lock(&rw->rw_mutex) != 0){
return res;
}
//查看rw_refcount的值看是否為0(即是否有線程對其進行操作),如果rw_refcount不為0或有讀線程在等待,
//則將rw_nwaitwriters值加1,並將程序阻塞等待接收到寫條件變量後再對其上鎖進行讀操作
//(因為此程序實現的是讀優先,所以只有當沒有線程操作且沒有讀線程等待時,才能讓寫線程獲得資源進行寫操作)
while(rw->rw_refcount != 0 || rw->rw_nwaitreaders > 0){
rw->rw_nwaitwriters ++;
res = pthread_cond_wait(&rw->rw_condwriters, &rw->rw_mutex);
rw->rw_nwaitwriters--;
if(res != 0){
break;
}
}
//如果讀鎖加成功後,給refcount置為-1,因為-1代表寫操作
//(因為只能同時有且僅有一個線程進行寫操作,所以,加讀鎖後rw_refcount只能為-1)
if(res == 0){
rw->rw_refcount = -1;
}
//對控制my_pthread_rwlock結構體操作的rw_mutex成員解鎖,保證讓其他線程可以操作my_pthread_rwlock結構體
pthread_mutex_unlock(&rw->rw_mutex);
return res;
}
int my_pthread_rwlock_tryrdlock(my_pthread_rwlock_t *rw)
{
int res;
if(rw->rw_magic != RW_MAGIC){
//如果rw_magic不為初始值說明有線程正在進行操作,直接返回EINVAL錯誤並結束函數
return EINVAL;
}
//無論何時操作my_pthread_rwlock結構體都要對rw_mutex成員上鎖
if((res = pthread_mutex_lock(&rw->rw_mutex)) != 0){
return res;
}
//此處是嘗試上所函數中讀寫優先的不同地方。
//如果有寫線程正在執行,則返回EBUSY
if(rw->rw_refcount < 0){
res = EBUSY;
}else{ //否則,讓rw_refcount加1,讓其准確的記錄當前正在執行的讀進程個數
rw->rw_refcount ++;
}
//對控制my_pthread_rwlock結構體操作的rw_mutex成員解鎖,保證讓其他線程可以操作my_pthread_rwlock結構體
pthread_mutex_unclock(&rw->rw_mutex);
return res;
}
int my_pthread_rwlock_trywrlock(my_pthread_rwlock_t *rw)
{
int res;
if(rw->rw_magic != RW_MAGIC){ //如果rw_magic不為初始值說明有線程正在進行操作,直接返回EINVAL錯誤並結束函數
return EINVAL;
}
//無論何時操作my_pthread_rwlock結構體都要對rw_mutex成員上鎖
if((res = pthread_mutex_lock(&rw->rw_mutex)) != 0){
return res;
}
//如果正在操作的線程數不為0或等待讀操作的線程個數不為0,則返回EBUSY
if(rw->rw_refcount != 0 || rw->nwaitreaders > 0){
res = EBUSY;
}else{ //否則,讓rw_refcount = -1,讓其狀態為有寫進程操作
rw->rw_refcount = -1;
}
//對控制my_pthread_rwlock結構體操作的rw_mutex成員解鎖,保證讓其他線程可以操作my_pthread_rwlock結構體
pthread_mutex_unclock(&rw->rw_mutex);
return res;
}
//解鎖函數
int my_pthread_rwlock_unlock(my_pthread_rwlock_t *rw)
{
int res;
if(rw->rw_magic != RW_MAGIC){
//如果rw_magic不為初始值說明有線程正在進行操作,直接返回EINVAL錯誤並結束函數
return EINVAL;
}
//無論何時操作my_pthread_rwlock結構體都要對rw_mutex成員上鎖
if((res = pthread_mutex_lock(&rw->rw_mutex)) != 0){
return res;
}
if(rw->rw_refcount == -1){ //如果rw_refcount = -1,說明執行的是寫操作,將其值賦為0,解除寫線程的鎖
rw->rw_refcount = 0;
}else if(rw->rw_refcount > 0){ //如果此時rw_refcount大於0,所以現在再執行的操作為讀操作,給rw_refcount減1,解除讀線程的鎖
rw->rw_refcount--;
}else{
rw->rw_refcount = 0;
}
if(rw->rw_nwaitreaders > 0){ //如果有讀線程等待則廣播發送讀條件變量,通知所有等待讀操作的線程有空閒資源
res = pthread_cond_broadcast(&rw->rw_condreaders);
}else if(rw->rw_nwaitwriters > 0){ //如果沒有讀線程等待且等待寫操作的線程數不為0時,
//發送寫條件變量,通知寫線程有空閒資源
if(rw->rw_refcount == 0){
res = pthread_cond_signal(&rw->rw_condwriters);
}
}
//對控制my_pthread_rwlock結構體操作的rw_mutex成員解鎖,保證讓其他線程可以操作my_pthread_rwlock結構體
pthread_mutex_unlock(&rw->rw_mutex);
return res;
}
測試代碼:
test.cpp:
#include
#include
#include
#include "pthread_rwlock.h"
my_pthread_rwlock_t rwlock;
//
void* thread_fun1(void *arg)
{
my_pthread_rwlock_wrlock(&rwlock);
printf("This is fun1.\n");
sleep(5);
my_pthread_rwlock_unlock(&rwlock);
}
void* thread_fun2(void *arg)
{
printf("thread2 is run......\n");
my_pthread_rwlock_rdlock(&rwlock);
printf("This is fun2.\n");
my_pthread_rwlock_unlock(&rwlock);
}
void* thread_fun3(void *arg)
{
printf("thread3 is run......\n");
my_pthread_rwlock_rdlock(&rwlock);
printf("This is fun3.\n");
my_pthread_rwlock_unlock(&rwlock);
}
void* thread_fun4(void *arg)
{
printf("thread4 is run......\n");
my_pthread_rwlock_wrlock(&rwlock);
printf("This is fun4.\n");
my_pthread_rwlock_unlock(&rwlock);
}
int main()
{
my_pthread_rwlock_init(&rwlock, NULL);
pthread_t tid[4];
pthread_create(&tid[0], NULL, thread_fun1, NULL);
sleep(1);
pthread_create(&tid[1], NULL, thread_fun2, NULL);
pthread_create(&tid[2], NULL, thread_fun3, NULL);
pthread_create(&tid[3], NULL, thread_fun4, NULL);
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
pthread_join(tid[2], NULL);
pthread_join(tid[3], NULL);
my_pthread_rwlock_destroy(&rwlock);
return 0;
}
運行結果:
用寫者優先程序測試結果:
用讀者優先程序測試結果: