[nptl][rwlock]pthread rwlock原理分析
概念:
In computer science, a readers–writer (RW) or shared-exclusive lock (also known as a multiple readers/single-writer lock or multi-reader lock) is a synchronization primitive that solves one of the readers–writers problems. An RW lock allows concurrent access for read-only operations, while write operations require exclusive access. This means that multiple threads can read the data in parallel but an exclusive lock is needed for writing or modifying data. When a writer is writing the data, all other writers or readers will be blocked until the writer is finished writing. A common use might be to control access to a data structure in memory that cannot be updated atomically and is invalid (and should not be read by another thread) until the update is complete.
摘自Wikipedia的一段(原文https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock)。
简而言之,就是一次只有一个writer可以占有写模式的读写锁,但是多个reader可用同时占有读模式的读写锁。
分析:
代码选自glibc-2.23。以x86为例。
数据结构定义在glibc-2.23/sysdeps/x86/bits/pthreadtypes.h
typedef union
{
struct
{
int __lock;
unsigned int __nr_readers;
unsigned int __readers_wakeup;
unsigned int __writer_wakeup;
unsigned int __nr_readers_queued;
unsigned int __nr_writers_queued;
int __writer;
int __shared;
signed char __rwelision;
unsigned long int __pad2;
/* FLAGS must stay at this position in the structure to maintain
binary compatibility. */
unsigned int __flags;
# define __PTHREAD_RWLOCK_INT_FLAGS_SHARED 1
} __data;
char __size[__SIZEOF_PTHREAD_RWLOCK_T];
long int __align;
} pthread_rwlock_t;
其中,
__nr_readers字段表示当前有多少个reader正在使用lock;
__readers_wakeup字段表示需要被唤醒的reader的个数;
__writer_wakeup字段表示需要被唤醒的writer的个数;
__nr_readers_queued字段表示正在排队等待锁的reader个数;
__nr_writers_queued字段表示正在排队等待锁的writer的个数;
__writer字段表示正在使用锁的writer的线程id,用来防止死锁;
read锁函数实现在glibc-2.23/nptl/pthread_rwlock_rdlock.c
/* 函数实现的关键部分,部分代码省略 */
int __pthread_rwlock_rdlock (pthread_rwlock_t *rwlock)
{
/* Make sure we are alone. */
lll_lock (rwlock->__data.__lock, rwlock->__data.__shared);
/* Get the rwlock if there is no writer... */
if (rwlock->__data.__writer == 0 //当前不是writer占用lock
&& (!rwlock->__data.__nr_writers_queued //当前的等待lock的writer个数是0
|| PTHREAD_RWLOCK_PREFER_READER_P (rwlock))) //或者iattr->lockkind不是PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP类型,默认是PTHREAD_RWLOCK_DEFAULT_NP
{
/* Increment the reader counter. Avoid overflow. */
if (__glibc_unlikely (++rwlock->__data.__nr_readers == 0))//这里的代码实现很谨慎,防止溢出
{
/* Overflow on number of readers. */
--rwlock->__data.__nr_readers;
result = EAGAIN;
}
else
{
LIBC_PROBE (rdlock_acquire_read, 1, rwlock);
if (rwlock->__data.__nr_readers == 1
&& rwlock->__data.__nr_readers_queued > 0
&& rwlock->__data.__nr_writers_queued > 0)
{//优先唤醒正在排队中的reader
++rwlock->__data.__readers_wakeup;
wake = true;
}
}
/* We are done, free the lock. */
lll_unlock (rwlock->__data.__lock, rwlock->__data.__shared);
if (wake)
futex_wake (&rwlock->__data.__readers_wakeup, INT_MAX, futex_shared);
return result;//所以,如果不是writer占用锁或者lockkind是PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP的情况下,reader都可以获取到锁。
}
return __pthread_rwlock_rdlock_slow (rwlock);//
}
static int __attribute__((noinline))
__pthread_rwlock_rdlock_slow (pthread_rwlock_t *rwlock)
{
/* Lock is taken in caller. */
while (1)
{
if (__builtin_expect (rwlock->__data.__writer
== THREAD_GETMEM (THREAD_SELF, tid), 0))
{//死锁检查无处不在
result = EDEADLK;
break;
}
/* Remember that we are a reader. */
if (__glibc_unlikely (++rwlock->__data.__nr_readers_queued == 0))
{//如果获取不到锁,就会增加__nr_readers_queued,但是防止溢出
/* Overflow on number of queued readers. */
--rwlock->__data.__nr_readers_queued;
result = EAGAIN;
break;
}
int waitval = rwlock->__data.__readers_wakeup;
/* Free the lock. */
lll_unlock (rwlock->__data.__lock, rwlock->__data.__shared);
/* Wait for the writer to finish. We do not check the return value
because we decide how to continue based on the state of the rwlock. */
futex_wait_simple (&rwlock->__data.__readers_wakeup, waitval,
futex_shared);//这里是使用futex wait在等待唤醒,即排队中
/* Get the lock. */
lll_lock (rwlock->__data.__lock, rwlock->__data.__shared);
--rwlock->__data.__nr_readers_queued;//代码跑到这里,那么已经被唤醒了,所以要减少__nr_readers_queued
/* Get the rwlock if there is no writer... */
if (rwlock->__data.__writer == 0
/* ...and if either no writer is waiting or we prefer readers. */
&& (!rwlock->__data.__nr_writers_queued
|| PTHREAD_RWLOCK_PREFER_READER_P (rwlock)))
{//哪怕reader被唤醒,依然要检查writer,因为rwlock原则上就不能让writer占用锁的时候,reader可以拿到锁
/* Increment the reader counter. Avoid overflow. */
if (__glibc_unlikely (++rwlock->__data.__nr_readers == 0))
{
/* Overflow on number of readers. */
--rwlock->__data.__nr_readers;
result = EAGAIN;
}
else
{
LIBC_PROBE (rdlock_acquire_read, 1, rwlock);
/* See pthread_rwlock_rdlock. */
if (rwlock->__data.__nr_readers == 1
&& rwlock->__data.__nr_readers_queued > 0
&& rwlock->__data.__nr_writers_queued > 0)
{
++rwlock->__data.__readers_wakeup;
wake = true;
}
}
break;
}
}
/* We are done, free the lock. */
lll_unlock (rwlock->__data.__lock, rwlock->__data.__shared);
if (wake)
futex_wake (&rwlock->__data.__readers_wakeup, INT_MAX, futex_shared);
return result;
}
write锁函数实现在glibc-2.23/nptl/pthread_rwlock_wrlock.c
总体来说,实现起来就简单一些,只要不是有其他writer占用锁并且没有reader占用锁,就可以获取到。否则等待锁释放,唤醒自己。
unlock锁函数实现在glibc-2.23/nptl/pthread_rwlock_unlock.c
/* 函数实现的关键部分,部分代码省略 */
int __pthread_rwlock_unlock (pthread_rwlock_t *rwlock)
{
if (rwlock->__data.__writer)
rwlock->__data.__writer = 0;//如果是writer占用,则释放writer。
else
--rwlock->__data.__nr_readers;//不是writer占用,则reader占用个数减少
if (rwlock->__data.__nr_readers == 0)
{//这里要么是writer释放的锁,要么是最后一个reader释放锁
if (rwlock->__data.__nr_writers_queued)
{//这里的逻辑是先判断是否有writer在排队,并优先唤醒writer
++rwlock->__data.__writer_wakeup;
lll_unlock (rwlock->__data.__lock, rwlock->__data.__shared);
futex_wake (&rwlock->__data.__writer_wakeup, 1, futex_shared);
return 0;
}
else if (rwlock->__data.__nr_readers_queued)
{//再判断是否有reader在排队,有的话,就唤醒。
++rwlock->__data.__readers_wakeup;
lll_unlock (rwlock->__data.__lock, rwlock->__data.__shared);
futex_wake (&rwlock->__data.__readers_wakeup, INT_MAX,
futex_shared);
return 0;
}
}
lll_unlock (rwlock->__data.__lock, rwlock->__data.__shared);
return 0;
}
总结:
1,总体大原则还是:一次只有一个writer可以占有写模式的读写锁,但是多个reader可用同时占有读模式的读写锁。
2,如果有reader在占用锁,那么其它的很多reader继续来使用,如果reader量真的很大的时候,那么writer可能会饿死,一直得不到执行。这里可以在rdlock中看到。
3,如果有writer在占用锁,后面又来了很多writer,那么reader会一直得不到执行,reader可能会饿死。这里在unlock中可以看到。
4,使用场景应该是读多写少的情况下,并且尽可能让锁的占用时间短一些。
- 【源码分享】机器学习之Python支持向量机
- [接口测试 - 基础篇] 08 封装个基本的excel解析类
- 关关的刷题日记10——Leetcode 1. Two Sum 方法1
- BZOJ 2463: [中山市选2009]谁能赢呢?(新生必做的水题)
- 10个Python面试常问的问题
- 关关的刷题日记11——Leetcode 1. Two Sum 方法2、3
- 计蒜客:百度的科学计算器(简单)【python神解】
- 关关的刷题日记12——Leetcode 189. Rotate Array 方法1、2、3
- 关关的刷题日记13——Leetcode 414. Third Maximum Number
- 关关的刷题日记14——Leetcode 167. Two Sum II - Input array is sorted
- 51Nod 1083 矩阵取数问题(矩阵取数dp,基础题)
- 【专知-关关的刷题日记15】Leetcode 27. Remove Element 方法1、2、3
- [接口测试 - 基础篇] 07 来来来,一起读写excel玩玩之一
- 时间序列分析算法【R详解】
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 一文教你搞懂C语言的Q格式
- 基础算法之排序算法
- C++核心准则E4,5:设计并构建不变量
- Selenium实际应用注入并执行Javascript语句
- 什么是Python的 “内存管理机制”
- 2020年手工webpack构建react项目,完美支持ssr,包括css和图片资源
- php中赋值、浅拷贝与深拷贝
- 2020最新:100道有答案的前端面试题(下)
- UI自动化测试之ddt实战
- KVM 之网络配置
- Mysql操作
- CentOS7+nginx+uwsgi+Django部署之路
- 【翻译】200行代码讲透RUST FUTURES (4)
- 使用Flask部署图像分类模型
- Try2Cry:使用蠕虫技术传播的勒索软件