POSIX多线程笔记(6):读写锁

读写锁的概念

在一些程序中存在一个称作读者写者的问题,即对于某些资源的访问,存在两种可能的情况,一种是访问必须是排他的,比如两个进程不能同时改变一个全局变量的数值,称作写操作,另一种是访问可以是共享的,称作读操作。显而易见,这一问题和相应的表述是从对文件的读写操作中引申出来的。

在多线程中,有一些公共数据修改的机会比较少,而读的机会却是非常多的,此公共数据的操作基本都是读,如果每次操作都给此段代码加锁,太浪费时间了而且也很浪费资源,降低程序的效率,因为读操作不会修改数据,只是做一些查询,所以在读的时候不用给此段代码加锁,可以共享的访问,只有涉及到写的时候,互斥的访问就好了

处理读者写者问题的两种常见的策略是:强读者同步(strong reader synchronization)和强写者同步(strong writer synchronization)。在强读者同步中,总是给读者以优先权,只要写者当前没有进行写操作,读者就可以获得访问权。在强写者同步中,通常将优先权交给写者,而将读者延迟到所有等待的或活动的写者都完成为止。

下面这个表格表示了读写操作请求在各种锁状态下的情况:

当前锁状态 读锁请求 写锁请求
无锁 可以 可以
读锁 可以 阻塞
写锁锁 阻塞 阻塞

初始化和销毁读写锁

读写锁由pthread_rwlock_t类型的变量表示。程序在使用pthread_rwlock_t变量进行同步之前,必须调用pthread_rwlock_init函数来初始化这个变量。这个函数的形式为:

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

参数rwlock是一个指向读写锁的指针,参数attr是一个读写锁属性对象的指针,如果将NULL传递给它,则使用默认属性来初始化一个读写锁。如果成功,pthread_rwlock_init就返回0。如果不成功,pthread_rwlock_init就返回一个非零的错误码。

pthread_rwlock_destroy函数用于销毁一个读写锁。它的形式为:

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); 

参数rwlock为指向要销毁的读写锁的指针。可以用pthread_rwlock_init对已经被销毁的读写锁重新进行初始化。如果成功,pthread_rwlock_destroy就返回0。如果不成功,pthread_rwlock_destroy就返回一个非零的错误码。

读写锁的操作

pthread_rwlock_rdlockpthread_rwlock_tryrdlock函数用来为读操作获取一个读写锁。pthread_rwlock_wrlockpthread_rwlock_trywrlock函数用来为写操作获取一个读写锁。pthread_rwlock_rdlockpthread_rwlock_wrlock阻塞线程,直到获取读写锁,而pthread_rwlock_tryrdlockpthread_rwlock_trywrlock则会立即返回。pthread_rwlock_unlock函数会将锁释放掉。这些函数的形式为:

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); 
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); 
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); 
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); 
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); 

如果成功,这些函数返回0,如果不成功,这些函数就返回一个非零的错误码。如果因为锁已经被持有而无法获取,pthread_rwlock_tryrdlock 和pthread_rwlock_trywrlock就返回EBUSY。

读写锁的实例

在下面这个例子中count为全局变量,三个读线程对count进行读操作并不阻塞,但是三个写线程对count进行自加一操作的时候进行阻塞,并互斥进行。

#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是写者线程的编号
    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是读者线程的编号
    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;
}

运行结果:
POSIX多线程笔记(6):读写锁