Linux同步技术之读写锁

互斥锁试图将想进入临界区的所有线程都阻塞住,但是有时候该临界区会涉及由这些线程共享的一个或多个数据的访问或更新,这时候我们就需要用到读写锁

系统读写锁的分配规则:

(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 <unistd.h>
#include <stdio.h>
#include <pthread.h>
#include <errno.h>

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;
}
时间: 2024-08-25 14:34:42

Linux同步技术之读写锁的相关文章

Linux环境编程之同步(三):读写锁

概述 互斥锁把试图进入我们称之为临界区的所有其他线程都阻塞住.该临界区通常涉及对由这些线程共享一个或多个数据的访问或更新.读写锁在获取读写锁用于读某个数据和获取读写锁用于写直接作区别.读写锁的分配规则如下: 1.只要没有线程持有某个给定的读写锁用于写,那么任意数目的线程可以持有该读写锁用于读. 2.仅当没有线程持有某个给定的读写锁用于读或用于写时,才能分配该读写锁用于写. 即只要没有线程在修改某个给定的数据,那么任意数目的线程都可以拥有该数据的读访问权.仅当没有其他线程在读或修改某个给定的数据时

读写锁(read-write lock)机制-----多线程同步问题的解决

读写锁(read-write lock)一 综述    在一些程序中存在读者写者问题,也就是说,对某些资源的访问会  存在两种可能的情况,一种是访问必须是排它行的,就是独占的意思,这称作写操作:另一种情况就是访问方式可以是共享的,就是说可以有多个线程同时去访问某个资源,这种就称作读操作.这个问题模型是从对文件的读写操作中引申出来的.    读写锁比起mutex具有更高的适用性,具有更高的并行性,可以有多个线程同时占用读模式的读写锁,但是只能有一个线程占用写模式的读写锁,读写锁的三种状态:1.当读

读写锁详解

1. 综述 在一些程序中存在读者写者问题,也就是说,对某些资源的访问会存在两种可能的情况,一种是访问必须是排它行的,就是独占的意思,这称作写操作:另一种情况就是访问方式可以是共享的,就是说可以有多个线程同时去访问某个资源,这种就称作读操作.这个问题模型是从对文件的读写操作中引申出来的.    读写锁比起mutex具有更高的适用性,具有更高的并行性,可以有多个线程同时占用读模式的读写锁,但是只能有一个线程占用写模式的读写锁,读写锁的三种状态: (1)当读写锁是写加锁状态时,在这个锁被解锁之前,所有

Linux程序设计学习笔记----多线程编程线程同步机制之互斥量(锁)与读写锁

互斥锁通信机制 基本原理 互斥锁以排他方式防止共享数据被并发访问,互斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个互斥锁逻辑上绑定之后,对该资源的访问操作如下: (1)在访问该资源之前需要首先申请互斥锁,如果锁处于开状态,则申请得到锁并立即上锁(关),防止其他进程访问资源,如果锁处于关,则默认阻塞等待. (2)只有锁定该互斥锁的进程才能释放该互斥锁. 互斥量类型声明为pthread_mutex_t数据类型,在<bits/pthreadtypes.h>中有具体的定义. 互斥量

linux系统编程:线程同步-读写锁(rwlock)

线程同步-读写锁(rwlock) 读写锁 读写锁是互斥量的细化:显然,只有对全局资然进行写入操作时,才需要同步:在对全局资然进行读取操作时,是不需要锁的. 相关函数 pthread_rwlock_t //读写锁类型 pthread_rwlock_init //初始化 pthread_rwlock_destroy //销毁锁 pthread_rwlock_rdlock //获取读锁 pthread_rwlock_wrlock //获取写锁 pthread_rwlock_tryrdlock pthr

linux线程同步(3)-读写锁

一.概述                                                    读写锁与互斥量的功能类似,对临界区的共享资源进行保护!互斥量一次只让一个线程进入临界区,读写锁比它有更高的并行性.读写锁有以下特点: 1.如果一个线程用读锁锁定了临界区,那么其他线程也可以用读锁来进入临界区,这样就可以多个线程并行操作.但这个时候,如果再进行写锁加锁就会发生阻塞,写锁请求阻塞后,后面如果继续有读锁来请求,这些后来的读锁都会被阻塞!这样避免了读锁长期占用资源,防止写锁饥饿

Linux系统编程——线程同步与互斥:读写锁

当有一个线程已经持有互斥锁时,互斥锁将所有试图进入临界区的线程都阻塞住.但是考虑一种情形,当前持有互斥锁的线程只是要读访问共享资源,而同时有其它几个线程也想读取这个共享资源,但是由于互斥锁的排它性,所有其它线程都无法获取锁,也就无法读访问共享资源了,但是实际上多个线程同时读访问共享资源并不会导致问题. 在对数据的读写操作中,更多的是读操作,写操作较少,例如对数据库数据的读写应用.为了满足当前能够允许多个读出,但只允许一个写入的需求,线程提供了读写锁来实现.... www.worlduc.com/

linux线程间同步(1)读写锁

读写锁比mutex有更高的适用性,可以多个线程同时占用读模式的读写锁,但是只能一个线程占用写模式的读写锁. 1. 当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞: 2. 当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是以写模式对它进行枷锁的线程将阻塞: 3. 当读写锁在读模式锁状态时,如果有另外线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求,这样可以避免读模式锁长期占用,而等待的写模式锁请求长期阻塞: 这种锁适用对数据结

Linux IPC 同步(二):读写锁

读写锁的分配规则如下: 1.  只要没有线程持有某个指定的读写锁用于写,那么任意数目的线程可以持有该读写锁用于读: 2.  仅当没有线程持有某个指定的读写锁用于读或者用于写,才能分配读写锁用于写. 这样的访问方式也称为共享-独占上锁(shared-exclusion) 那么我想到了这样一个场景: 线程A要写数据,上锁未果被阻塞,期间不断有读者来读,线程A饿死......  (实验验证之) int pthread_rwlock_init(pthread_rwlock_t *, pthread_rw