淺談讀寫鎖的使用

一、讀寫鎖是什麼?

讀寫鎖其實還是一種鎖,是給一段臨界區代碼加鎖,但是此加鎖是在進行寫操作的時候纔會互斥,而在進行讀的時候是可以共享的進行訪問臨界區的。
讀寫鎖和互斥量(互斥鎖)很類似,是另一種線程同步機制,但不屬於POSIX標準,可以用來同步同一進程中的各個線程。當然如果一個讀寫鎖存放在多個進程共享的某個內存區中,那麼還可以用來進行進程間的同步,

讀寫鎖的使用規則:

  1. 只要沒有寫模式下的加鎖,任意線程都可以進行讀模式下的加鎖;
  2. 只有讀寫鎖處於不加鎖狀態時,才能進行寫模式下的加鎖;
  3. 讀寫鎖也稱爲共享-獨佔(shared-exclusive)鎖,當讀寫鎖以讀模式加鎖時,它是以共享模式鎖住,當以寫模式加鎖時,它是以獨佔模式鎖住。讀寫鎖非常適合讀數據的頻率遠大於寫數據的頻率從的應用中。這樣可以在任何時刻運行多個讀線程併發的執行,給程序帶來了更高的併發度。

ps:讀寫鎖本質上是一種自旋鎖

二、爲什麼需要讀寫鎖?

有時候,在多線程中,有一些公共數據修改的機會比較少,而讀的機會卻是非常多的,此公共數據的操作基本都是讀,如果每次操作都給此段代碼加鎖,太浪費時間了而且也很浪費資源,降低程序的效率,因爲讀操作不會修改數據,只是做一些查詢,所以在讀的時候不用給此段代碼加鎖,可以共享的訪問,只有涉及到寫的時候,互斥的訪問就好了

三、讀寫鎖的行爲

讀寫之間是互斥的—–>讀的時候寫阻塞,寫的時候讀阻塞,而且讀和寫在競爭鎖的時候,寫會優先得到鎖
在這裏插入圖片描述

四、自旋鎖&掛起等待鎖(互斥鎖)?

1.自旋鎖

自旋鎖是在發生獲取不到鎖的時候,會直接等待,不會被CPU直接調度走,而是會一直等到獲取到鎖,因爲此鎖是一直的在等待,所以不會有調度的開銷,故此鎖的效率比掛起等待鎖的效率高,但是此鎖會因不停的查看鎖的釋放情況,故會浪費更多的CPU資源

2.掛起等待鎖

掛起等待鎖是當某線程在執行臨界區的代碼時,那其他線程只能掛起等待,此時這些線程會被CPU調度走,等到鎖釋放(即就是臨界區的代碼被之前的那個線程已經執行完畢),而且被CPU調度的線程只有被調度回來纔可以執行臨界區的代碼
掛起等待鎖是在發生獲取不到鎖的時候,他會被CPU調度走,去做別的事,但是會時不時的去查看鎖有沒有被釋放
ps:線程想執行臨界區的代碼的條件:
(1)鎖釋放;
(2)被CPU調度回來

3.自旋鎖的優缺點

優點:效率高,避免了線程之間調度的開銷
缺點:浪費CPU資源

4.掛起等待鎖的優缺點

優點:不會浪費CPU的資源,比較靈活
缺點:效率不高,很可能會使臨界區的代碼不被任何線程執行,因爲可能會是線程被
CPU調度走了但是卻沒有被調度回來

和互斥量不同的是:互斥量會把試圖進入已保護的臨界區的線程都阻塞;然而讀寫鎖會視當前進入臨界區的線程和請求進入臨界區的線程的屬性來判斷是否允許線程進入。

相對互斥量只有加鎖和不加鎖兩種狀態,讀寫鎖有三種狀態:讀模式下的加鎖,寫模式下的加鎖,不加鎖。

五、讀寫鎖是怎麼實現?

1.一種交易場所(存放數據的地方):可以是變量、鏈表、數組或其他數據結構
2.兩種角色:讀操作和寫操作
3.三種關係:
(1)讀和讀之間沒有關係
(2) 寫和寫之間是互斥關係
(3)讀和寫之間是同步互斥關係

ps:同步---->讀和寫在同時競爭鎖的時候,寫會優先的得到鎖
互斥---->讀的時候寫阻塞,寫的時候讀阻塞

相關函數
(1)pthread_rwlock_init()—->初始化函數

功能:初始化讀寫鎖
頭文件:#include<pthread.h>

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthred_rwlockattr_t *restrict attr);

參數說明:
rwlock:是要進行初始化的
attr:是rwlock的屬性
ps:此參數一般不關注,可設爲NULL

(2)pthread_rwlock_destroy—->銷燬函數

功能:銷燬初始化的鎖
頭文件:#include<pthread.h>

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

參數說明:
rwlock:是需要進行銷燬的鎖

(3)加鎖和解鎖

在進行讀操作的時候加的鎖:

pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);

在進行寫操作的時候加的鎖:

pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);

對讀/寫統一進行解鎖:

pthread_rwlock_unlock(pthread_rwlock_t* rwlock);

六、代碼實現讀寫鎖

#include<stdio.h>
#include<unistd.h>
#include<malloc.h>
#include<stdlib.h>
#include<pthread.h>
pthread_rwlock_t rwlock;//聲明讀寫鎖
int count;
//寫者線程的入口函數
void*route_write(void*arg)
{
    int i=*(int*)arg;//i是寫者線程的編號
    free(arg);
    while(1){
        int t=count;
        //加鎖
        pthread_rwlock_wrlock(&rwlock);
        printf("route_write:%d,%#x,count=%d,++count=%d\n",i,\
                pthread_self(),t,++count);
        //解鎖
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
}
//讀者線程的入口函數
void*route_read(void*arg)
{
    int i=*(int*)arg;//i是讀者線程的編號
    free(arg);
    while(1){
        //加鎖
        pthread_rwlock_rdlock(&rwlock);
        printf("route_read:%d,%#x,count=%d\n",i,pthread_self(),count);
        //解鎖
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
}

int main()
{
    int i=0;
    //初始化讀寫鎖
    pthread_rwlock_init(&rwlock,NULL);
    pthread_t tid[8];
    //創建3個寫者線程
    for(i=0;i<3;i++){
        int*p=(int*)malloc(sizeof(int));
        *p=i;
        pthread_create(&tid[i],NULL,route_write,(void*)p);
    }
    //創建3個讀者線程
    for(i=0;i<5;i++){
        int*p=(int*)malloc(sizeof(int));
        *p=i;
        pthread_create(&tid[i+3],NULL,route_read,(void*)p);
    }
    //主線程等待新創建的線程
    for(i=0;i<8;i++)
        pthread_join(tid[i],NULL);
    //銷燬讀寫鎖
    pthread_rwlock_destroy(&rwlock);
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章