歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Unix知識 >> Unix基礎知識 >> UNIX環境高級編程:線程同步之讀寫鎖及屬性

UNIX環境高級編程:線程同步之讀寫鎖及屬性

日期:2017/3/3 15:19:59   编辑:Unix基礎知識

讀寫鎖和互斥量(互斥鎖)很類似,是另一種線程同步機制,但不屬於POSIX標准,可以用來同步同一進程中的各個線程。當然如果一個讀寫鎖存放在多個進程共享的某個內存區中,那麼還可以用來進行進程間的同步, 互斥量要麼是鎖住狀態要麼是不加鎖狀態,而且一次只有一個線程可以對其加鎖。讀寫鎖可以有三種狀態:讀模式下的加鎖狀態,寫模式下的加鎖狀態,不加鎖狀態。

一次只有一個線程可以占有寫模式的讀寫鎖,但是多個線程可以同時占有讀模式的讀寫鎖。

當讀寫鎖是寫加鎖狀態時,在這個鎖被解鎖之前,所有試圖對這個鎖加鎖(讀或寫)的線程都會被阻塞。

當讀寫鎖在讀加鎖狀態時,所有試圖以讀模式對它加鎖的線程都可以得到訪問權,但是如果線程希望以寫模式對此鎖進行加鎖,它必須阻塞直到所有的線程釋放讀鎖。

當讀寫鎖在讀加鎖狀態時,如果有另外的線程試圖以寫模式加鎖,讀寫鎖通常會阻塞隨後的讀模式鎖請求。(後面有代碼驗證發現ubuntu 10.04系統下,不會阻塞隨後的讀模式的請求,最終導致請求寫鎖的線程餓死狀態)這樣可以避免讀模式鎖長期占有,而等待的寫模式鎖請求一直得不到滿足,出現餓死情況。後面會測試ubuntu 10.04系統的情況。(注意前篇關於記錄鎖fcntl中,ubuntu 10.04系統下,進程擁有讀鎖,然後優先處理後面的讀鎖,再處理寫鎖,導致寫鎖出現餓死)

讀寫鎖也稱為共享-獨占(shared-exclusive)鎖,當讀寫鎖以讀模式加鎖時,它是以共享模式鎖住,當以寫模式加鎖時,它是以獨占模式鎖住。讀寫鎖非常適合讀數據的頻率遠大於寫數據的頻率從的應用中。這樣可以在任何時刻運行多個讀線程並發的執行,給程序帶來了更高的並發度。

需要提到的是:讀寫鎖到目前為止仍然不是屬於POSIX標准。

1讀寫鎖的初始化和銷毀

#include <pthread.h>  
int pthread_rwlock_init (pthread_rwlock_t *rwlock,const pthread_rwlockattr_t *attr);  
      
int pthread_rwlock_destroy (pthread_rwlock_t *rwlock);  
      
                                               返回值:成功返回0,否則返回錯誤代碼

與互斥量一樣,讀寫鎖在使用之前必須初始化,在釋放它們底層的內存前必須銷毀。

上面兩個函數分別由於讀寫鎖的初始化和銷毀。和互斥量,條件變量一樣,如果讀寫鎖是靜態分配的,可以通過常量進行初始化,如下:

<span style="font-size:14px;">pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;</span>

也可以通過pthread_rwlock_init()進行初始化。對於動態分配的讀寫鎖由於不能直接賦值進行初始化,只能通過這種方式進行初始化。pthread_rwlock_init()第二個參數是讀寫鎖的屬性,如果采用默認屬性,可以傳入空指針NULL。

那麼當不在需要使用時及釋放(自動或者手動)讀寫鎖占用的內存之前,需要調用pthread_rwlock_destroy()進行銷毀讀寫鎖占用的資源。

2讀寫鎖的使用

/* 讀模式下加鎖  */
int pthread_rwlock_rdlock (pthread_rwlock_t *rwlock);  
      
/* 非阻塞的讀模式下加鎖  */
int pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock);  
      
/*  限時等待的讀模式加鎖 */
int pthread_rwlock_timedrdlock (pthread_rwlock_t *rwlock,const struct timespec *abstime);  
      
      
/* 寫模式下加鎖  */
int pthread_rwlock_wrlock (pthread_rwlock_t *rwlock);  
      
/* 非阻塞的寫模式下加鎖 */
int pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock);  
      
/* 限時等待的寫模式加鎖 */
int pthread_rwlock_timedwrlock (pthread_rwlock_t *rwlock,const struct timespec *abstime);  
      
/* 解鎖 */
int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);  
      
                                                   返回值:成功返回0,否則返回錯誤代碼

(1)pthread_rwlock_rdlock()系列函數

pthread_rwlock_rdlock()用於以讀模式即共享模式獲取讀寫鎖,如果讀寫鎖已經被某個線程以寫模式占用,那麼調用線程就被阻塞。如果讀寫鎖已經被某個線程以寫模式占用,那麼調用線程將獲得讀鎖。如果讀寫鎖未沒有被占有,但有多個寫鎖正在等待該鎖時,調用線程現在試圖獲取讀鎖,是否能獲取該鎖是不確定的。在實現讀寫鎖的時候可以對共享模式下鎖的數量進行限制(目前不知如何限制)。

pthread_rwlock_tryrdlock()和pthread_rwlock_rdlock()的唯一區別就是,在無法獲取讀寫鎖的時候,調用線程不會阻塞,會立即返回,並返回錯誤代碼EBUSY。

針對未初始化的讀寫鎖調用pthread_rwlock_rdlock/pthread_rwlock_tryrdlock,則結果是不確定的。

pthread_rwlock_timedrdlock()是限時等待讀模式加鎖,時間參數struct timespec * abstime也是絕對時間。

(2)pthread_rwlock_wrlock()系列函數

pthread_rwlock_wrlock()用於寫模式即獨占模式獲取讀寫鎖,如果讀寫鎖已經被其他線程占用,不論是以共享模式還是獨占模式占用,調用線程都會進入阻塞狀態。

pthread_rwlock_trywrlock()在無法獲取讀寫鎖的時候,調用線程不會進入睡眠,會立即返回,並返回錯誤代碼EBUSY。

針對未初始化的讀寫鎖調用pthread_rwlock_wrlock/pthread_rwlock_trywrlock,則結果是不確定的。

pthread_rwlock_timedwrlock()是限時等待寫模式加鎖。

(3)pthread_rwlock_unlock()

無論以共享模式還是獨占模式獲得的讀寫鎖,都可以通過調用pthread_rwlock_unlock()函數進行釋放該讀寫鎖。

針對未初始化的讀寫鎖調用pthread_rwlock_unlock,則結果是不確定的。

注意:當讀寫鎖以讀模式被占有N次,即調用pthread_rwlock_rdlock() N次,且成功。則必須調用N次pthread_rwlock_unlock()才能執行匹配的解鎖操作。

示例代碼:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <pthread.h>  
#include <errno.h>  
      
#define MAXDATA     1024  
#define MAXREDER    100  
#define MAXWRITER   100  
struct
{  
    pthread_rwlock_t   rwlock;   //讀寫鎖  
    char datas[MAXDATA];          //共享數據域  
}shared = {  
    PTHREAD_RWLOCK_INITIALIZER  
};  
      
void *reader(void *arg);  
void *writer(void *arg);  
      
int main(int argc,char *argv[])  
{  
    int i,readercount,writercount;  
    pthread_t tid_reader[MAXREDER],tid_writer[MAXWRITER];  
    if(argc != 3)  
    {  
        printf("usage : <reader_writer> #<readercount> #<writercount>\n");  
        exit(0);  
    }  
    readercount = atoi(argv[1]);  //讀者個數  
    writercount = atoi(argv[2]);   //寫者個數  
    pthread_setconcurrency(readercount+writercount);  
    for(i=0;i<writercount;++i)  
        pthread_create(&tid_writer[i],NULL,writer,NULL);  
    sleep(1); //等待寫者先執行  
    for(i=0;i<readercount;++i)  
        pthread_create(&tid_reader[i],NULL,reader,NULL);  
    //等待線程終止  
    for(i=0;i<writercount;++i)  
        pthread_join(tid_writer[i],NULL);  
    for(i=0;i<readercount;++i)  
        pthread_join(tid_reader[i],NULL);  
    exit(0);  
}  
void *reader(void *arg)  
{  
    pthread_rwlock_rdlock(&shared.rwlock);  //獲取讀出鎖  
    if( pthread_rwlock_rdlock(&shared.rwlock) ==0 ) //獲取讀出鎖  
           printf("pthread_rwlock_rdlock OK\n");  
    printf("Reader begins read message.\n");  
    printf("Read message is: %s\n",shared.datas);  
    pthread_rwlock_unlock(&shared.rwlock);  //釋放鎖  
    if( pthread_rwlock_unlock(&shared.rwlock) != 0 );  
        printf("pthread_rwlock_unlock fail\n");  
    return NULL;  
}  
      
void *writer(void *arg)  
{  
    char datas[MAXDATA];  
    pthread_rwlock_wrlock(&shared.rwlock);  //獲取寫鎖  
    if( pthread_rwlock_wrlock(&shared.rwlock) != 0)//再次獲取寫鎖  
        perror("pthread_rwlock_wrlock");  
    if( pthread_rwlock_rdlock(&shared.rwlock) !=0 ) //獲取讀出鎖  
        perror("pthread_rwlock_rdlock");  
    printf("Writers begings write message.\n");  
    sleep(1);  
    printf("Enter the write message: \n");  
    scanf("%s",datas);   //寫入數據  
    strcat(shared.datas,datas);  
    pthread_rwlock_unlock(&shared.rwlock);  //釋放鎖  
    return NULL;  
}

運行結果:

huangcheng@ubuntu:~$ gcc 2.c -lpthread  
huangcheng@ubuntu:~$ ./a.out 5 3  
pthread_rwlock_wrlock: Success  
pthread_rwlock_rdlock: Success  
Writers begings write message.  
Enter the write message:  
hu  
pthread_rwlock_wrlock: Success  
pthread_rwlock_rdlock: Success  
Writers begings write message.  
Enter the write message:  
1  
pthread_rwlock_wrlock: Success  
pthread_rwlock_rdlock: Success  
Writers begings write message.  
Enter the write message:  
2  
pthread_rwlock_rdlock OK  
Reader begins read message.  
Read message is: hu12  
pthread_rwlock_unlock fail  
pthread_rwlock_rdlock OK  
Reader begins read message.  
Read message is: hu12  
pthread_rwlock_unlock fail  
pthread_rwlock_rdlock OK  
Reader begins read message.  
Read message is: hu12  
pthread_rwlock_unlock fail  
pthread_rwlock_rdlock OK  
Reader begins read message.  
Read message is: hu12  
pthread_rwlock_unlock fail  
pthread_rwlock_rdlock OK  
Reader begins read message.  
Read message is: hu12  
pthread_rwlock_unlock fail

結果說明:

(1)當一個線程獲得讀寫鎖的寫模式,其他線程試圖獲得該讀寫鎖的讀模式或者是寫模式,都將會阻塞,直到該線程釋放該讀寫鎖。
(2)當一個線程獲得讀寫鎖的寫模式,該線程試圖獲得該讀寫鎖的讀模式或寫模式,都會立即返回失敗,不會導致失敗。
(3)當一個線程獲得讀寫鎖的讀模式,如果該線程試圖獲得該讀寫鎖的讀模式,則返回成功,並在該線程釋放該讀寫鎖只需要釋放一次,第二次會釋放失敗。
(4)當一個線程獲得讀寫鎖的讀模式,且還沒有釋放該讀寫鎖,如果該線程試圖獲得該讀寫鎖的寫模式,將導致阻塞,直到該線程釋放該讀寫鎖。
(5)當一個線程獲得讀寫鎖的寫模式,該線程試圖釋放該寫鎖兩次,將導致不可預測的問題。
(6)當一個線程獲得讀寫鎖的寫模式或者讀模式,不能讀該讀寫鎖釋放兩次。即不管該線程獲得讀鎖幾次,都只需要釋放該讀寫鎖一次就OK。

3讀寫鎖的屬性設置

/* 初始化讀寫鎖屬性對象 */
int pthread_rwlockattr_init (pthread_rwlockattr_t *attr);  
      
/* 銷毀讀寫鎖屬性對象 */
int pthread_rwlockattr_destroy (pthread_rwlockattr_t *attr);  
      
/* 獲取讀寫鎖屬性對象在進程間共享與否的標識*/
int pthread_rwlockattr_getpshared (__const pthread_rwlockattr_t *attr,int *pshared);  
      
/* 設置讀寫鎖屬性對象,標識在進程間共享與否  */
int pthread_rwlockattr_setpshared (pthread_rwlockattr_t *attr, int pshared);  
      
                                                    返回值:成功返回0,否則返回錯誤代碼

pthread_rwlockattr_setpshared()函數的第二個參數pshared用於設定是否進程間共享,其值可以是PTHREAD_PROCESS_PRIVATE或PTHREAD_PROCESS_SHARED,後者是設置進程間共享。

查看本欄目更多精彩內容:http://www.bianceng.cn/OS/unix/

示例代碼:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <pthread.h>  
#include <errno.h>  
      
struct{  
    pthread_rwlock_t rwlock;  
    int product;  
}sharedData = {PTHREAD_RWLOCK_INITIALIZER, 0};  
      
void * produce(void *ptr)  
{  
    int i;  
    for ( i = 0; i < 5; ++i)  
    {  
        pthread_rwlock_wrlock(&sharedData.rwlock);  
        sharedData.product = i;  
        printf("produce:%d\n",i);  
        pthread_rwlock_unlock(&sharedData.rwlock);  
      
        sleep(1);  
    }  
}  
      
void * consume1(void *ptr)  
{  
    int i;  
    for ( i = 0; i < 5;)  
    {  
        pthread_rwlock_rdlock(&sharedData.rwlock);  
        printf("consume1:%d\n",sharedData.product);  
        pthread_rwlock_unlock(&sharedData.rwlock);  
        ++i;  
        sleep(1);  
    }  
}  
      
void * consume2(void *ptr)  
{  
    int i;  
    for ( i = 0; i < 5;)  
    {  
        pthread_rwlock_rdlock(&sharedData.rwlock);  
        printf("consume2:%d\n",sharedData.product);  
        pthread_rwlock_unlock(&sharedData.rwlock);  
      
        ++i;  
        sleep(1);  
    }  
}  
      
int main()  
{  
    pthread_t tid1, tid2, tid3;  
      
    pthread_create(&tid1, NULL, produce, NULL);  
    pthread_create(&tid2, NULL, consume1, NULL);  
    pthread_create(&tid3, NULL, consume2, NULL);  
      
    void *retVal;  
      
    pthread_join(tid1, &retVal);  
    pthread_join(tid2, &retVal);  
    pthread_join(tid3, &retVal);  
      
    return 0;  
}

運行結果:

huangcheng@ubuntu:~$ ./a.out
consume2:0  
consume1:0  
produce:0  
consume2:0  
consume1:0  
produce:1  
consume2:1  
consume1:1  
produce:2  
consume2:2  
consume1:2  
produce:3  
consume2:3  
consume1:3  
produce:4  
huangcheng@ubuntu:~$

如果把consume1的解鎖注釋掉,如下:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <pthread.h>  
#include <errno.h>  
      
struct{  
    pthread_rwlock_t rwlock;  
    int product;  
}sharedData = {PTHREAD_RWLOCK_INITIALIZER, 0};  
      
void * produce(void *ptr)  
{  
    int i;  
    for ( i = 0; i < 5; ++i)  
    {  
        pthread_rwlock_wrlock(&sharedData.rwlock);  
        sharedData.product = i;  
        printf("produce:%d\n",i);  
        pthread_rwlock_unlock(&sharedData.rwlock);  
      
        sleep(1);  
    }  
}  
      
void * consume1(void *ptr)  
{  
    int i;  
    for ( i = 0; i < 5;)  
    {  
        pthread_rwlock_rdlock(&sharedData.rwlock);  
        printf("consume1:%d\n",sharedData.product);  
    //   pthread_rwlock_unlock(&sharedData.rwlock);  
        ++i;  
        sleep(1);  
    }  
}  
      
void * consume2(void *ptr)  
{  
    int i;  
    for ( i = 0; i < 5;)  
    {  
        pthread_rwlock_rdlock(&sharedData.rwlock);  
        printf("consume2:%d\n",sharedData.product);  
        pthread_rwlock_unlock(&sharedData.rwlock);  
      
        ++i;  
        sleep(1);  
    }  
}  
      
int main()  
{  
    pthread_t tid1, tid2, tid3;  
      
    pthread_create(&tid1, NULL, produce, NULL);  
    pthread_create(&tid2, NULL, consume1, NULL);  
    pthread_create(&tid3, NULL, consume2, NULL);  
      
    void *retVal;  
      
    pthread_join(tid1, &retVal);  
    pthread_join(tid2, &retVal);  
    pthread_join(tid3, &retVal);  
      
    return 0;  
}

程序運行結果:

huangcheng@ubuntu:~$ ./a.out
consume2:0  
consume1:0  
consume2:0  
consume1:0  
consume2:0  
consume1:0  
consume2:0  
consume1:0  
consume2:0  
consume1:0  
最後程序保持阻塞狀態

從執行結果可以看出Ubuntu 10.04提供的讀寫鎖函數是優先考慮等待讀模式占用鎖的線程,這種實現的一個很大缺陷就是出現寫入線程餓死的情況。

作者信息:csdn博客 ctthuangcheng

Copyright © Linux教程網 All Rights Reserved