读写锁

先说点其他的,就是发现之前看的IPC(interprocess communication)资料是System V的(有点老旧,从博客中的书页截图就可以看出来,但是这本书的确不错),虽然说在我的最新版Ubuntu上还可以用,但我去看了POSIX和System V的区别好像POSIX更好一些,所以后面的IPC的学习就使用POSIX的。

1.概述

互斥锁将试图进入我们称之为临界区的所有其他线程都阻塞住。该临界区通常涉及这些线程共享的一个或者多个数据的访问或更新。然而有时候我们可以在读某个数据与修改某个数据之间作以区分。

读写锁的两条分配规则

  ①只要没有线程持有某个给定的读写锁用于写,那么任意数目的线程可以持有该读写锁用于读

  ②仅当没有线程持有某个给定的读写锁用于读或用于写时,才能分配该读写锁用于写

//举个栗子,ATM机在没人用的时候大家可以围个圈去参观它,但是只有它在没人用它(参观的不算使用)时,其他人才可以用它

某些应用中读数据比写数据频繁的多,这些应用可以从读写锁代替互斥锁中获益。任意给定时刻允许多个读出者存在提供了更高的并发度,同时在某个写入者修改数据期间保护该数据该数据,以免其他读者或写者干扰。

这种对于对某个给定资源的共享访问也称为 共享-独占  上锁。因为获取一个读写锁用于读称之为 共享锁,获取一个读写锁用于写称之为 独占锁。

2.获取与释放读写锁

读写锁的数据类型为pthread_rwlock_t。如果这个类型的某个变量是静态分配的,那么可通过给它赋常值 PTHREAD_RWLOCK_INITIALIZER 来初始化它。

 pthread_rwlock_rdlock获取一个读出锁,如果对应的读写锁已由某个写入者持有,那么久阻塞调用线程。pthread_rwlock_wrlock 获取一个写入锁,如果对应的写入锁已有另一个写入锁持有,或者已由一个或多个读出者持有,那就阻塞调用线程。pthread_rwlock_unlock 释放一个读出所或写入锁。

#include<pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_unlock(pthread_rwlock_t *rwptr);
//成功返回0,失败返回一个对应错误的正值

 下面两个函数尝试获取一个读出锁或写入锁,但是如果该锁不能马上取得,那就返回一个EBUSY错误,而不是把调用线程投入睡眠。

#include<pthread.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwptr);
//成功返回0,失败返回一个对应错误的正值

 3.读写锁属性

除了使用静态分配的方法给读写锁初始化,还可以使用 pthread_rwlock_init 来动态地初始化。当一个线程不再需要某个读写锁时,可以调用 pthread_rwlock_destory 摧毁它。

#include<pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *rwptr, const pthread_rwlockattr_t *attr);
int pthread-rwlock_destory(pthread_rwlock_t *rwptr);
//成功返回0,失败返回一个对应错误的正值

 数据类型为 pthread_rwlockattr_t的某个属性对象一旦初始化,就通过调用不同的函数来启用或者禁止特定属性。当前定义了唯一的属性是 PTHREAD_PROCESS_SHARED ,它指定相应的读写锁在不同进程间共享,而不仅仅是在单个进程内的不同线程间共享。以下两个函数分别获取和设置这个属性。

#include<pthread.h>
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *valptr);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int value);
//成功返回0,失败返回一个对应错误的正值

 第一个函数在由valptr指向的整数中返回该属性的当前值。第二个函数把该属性的当前值设置为value, 其值为 PTHREAD_PROXESS_PRIVATE,或者为 PTHREAD_PROCESS_SHARED。

 由于读写锁的特性,我设计了两个程序,测试其是否可以共读,写锁和读锁在同时竞争时,写锁是否有更高优先级。

例程1.

#include"main.h"
#include"time_rw.h"
#include"pthread_rwlock.h"

int main()
{
	pthread_t th1, th2, th3, th4, th5;
	Pthread_rwlock_init(&rw, NULL);
//	Pthread_create(&th1, &pthread1);
//	sleep(1);
//	Pthread_create(&th2, &pthread2);
	Pthread_create(&th3, &pthread3);
     sleep(1); Pthread_create(&th4, &pthread4); Pthread_create(&th5, &pthread5); sleep(15); }

 对程序先予以说明,线程1,2是给文件写的线程,线程3,4,5为读线程。

现在让线程3先创建,在其加锁后打印并休眠20秒。这时候如果线程4,5还可以访问,则说明其确实有共读性。

在线程3未解锁的情况下,线程4,5仍然可以查看文件内容,足以说明其共读性。那么它是如何实现这个功能的呢?

 pthread_rwlock_rdlock函数的实现

int pthread_rwlock_rdlock(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);


	/*give preference to waitinn writers*/
	while(rw->rw_rwfcount<0 || rw->rw_nwaitwriters>0)
	{
		rw->rw_nwaitreaders++;
		result = pthread_cond_wait(&rw->rw_condreadsers, &rw->rw_mutex);
		rw->rw_nwaitreaders--;
		if(result != 0)
			break;
	}
	if(result == 0)
		rw->rw_rwfcount++;
	pthread_mutex_unlock(&rw->rw_mutex);
	return(result);
}

 对程序进行说明,当pthread_rwlock_rdlock函数被调用的时候,5~6 其先检查是否已经初始化,rw_magic是pthread_rwlock_t类型的初始化标志成员,若已经初始化,rw_magic会被置为RW_MAGIC( 0x1923764),摧毁时要将其置0。8~9 然后给rw_mutex成员上锁,接着 13 检查是否有写入者调用指定的读写锁 或者 有一个线程正等着获取该读写锁的写入锁,rw_rwfcount<0时说明线程正在使用该读写锁进行写入,rw_nwaitwriters>0说明有线程正在等待获取该读写锁的写入锁,如果有两者其一,15 rw_nwaitreaders数量加一,16 程序进行解锁后阻塞,等待通知,17 等到该锁后rw_nwaitreaders数量减一,继续检查是否有写入者调用指定的读写锁 或者 有一个线程正等着获取该读写锁的写入锁,这两个条件同时不成立, 或者 16 pthread_cond_wait函数出错 24 直接让result返回,21~22在没有线程需要该读写锁的写入锁的情况下,调用pthread_rwlock_rdlock函数的线程成功获得该读写锁的读锁,将rw_rwfcount数量加一,rw_rwfcount>0说明有线程持有该读写锁的写入锁,23 然后将rw_mutex释放, 24 返回result。

pthread_rwlock_rdlock函数的精髓就是通过互斥锁,条件变量和自身数据结构 构造了一个具有写入优先级,可共读的锁,但实际上我们从源码可以看到,无论是写锁还是读锁在使用的时候,并没有真正的上互斥锁。从下面的pthread_rwlock_unlock就可以更加清晰的看出其工作原理。

 pthread_rwlock_unlock函数实现

 

int pthread_rwlock_unlock(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
		err_dnum("rw_refcount = %d", rw->rw_refcount);//错误rw_refcount数值

		/*give preference to waiting writers over waiting readers*/
	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);
}

 

 通过pthread_rwlock_unlock我们可以看出,读写锁是通过在 加锁解锁时候的逻辑判断决定让谁进入临界区,并不是通过实际的互斥锁锁住临界区,

 

 

#include"main.h"
#include"time_rw.h"
#include"pthread_rwlock.h"

int main()
{
	pthread_t th1, th2, th3, th4, th5;
	Pthread_rwlock_init(&rw, NULL);
	Pthread_create(&th1, &pthread1);
	sleep(1);
	Pthread_create(&th2, &pthread2);
	Pthread_create(&th3, &pthread3);
	//sleep(1);
	//Pthread_create(&th4, &pthread4);
	//Pthread_create(&th5, &pthread5);
	sleep(15);
}

 现在先让线程1运行,线程1用完锁以后释放,让线程2和线程3争抢读写锁,发现因为线程2是写锁,看起来其在与读锁争抢时有更高的优先级

 

 但是实际上我将线程2,3的创建顺序进行调换以后

 

 发现并不是,其优先级与创建顺序有关?

 

posted @ 2019-11-07 21:56  C_hp  阅读(381)  评论(0编辑  收藏  举报