Postgres中的SpinLock锁
我们知道,在数据库中为了并发控制,少不了要使用各种各样的锁(lock)。PostgreSQL中也不例外。
在PostgreSQL中有三种级别的锁,他们的关系如下:
|上层 RegularLock
|
| LWLock
|
|底层 SpinLock
那么按照顺序,我们先来讨论下PostgreSQL的最底层的SpinLock。
作为PostgreSQL的最底层的锁,SpinLock比较简单,它的特点是封锁时间很短,没有等待队列和死锁检测机制,在事务结束时不能自动释放。因此,SpinLock一般不单独使用,而是作为其他锁(LWLock)的底层实现。
作为最底层锁,它的实现是和操作系统和硬件环境相关的。为此,PostgreSQL实现了两个SpinLock:
与机器相关的实现,利用TAS指令集实现(定义在s_lock.h和s_lock.c中);
与机器无关,利用PostgreSQL定义的信号量PGSemaphore实现(定义在spin.c中)。
很显然,依赖机器实现的SpinLock一定比不依赖机器实现的SpinLock要快。因此,如果PostgreSQL运行的机器上如果支持TAS指令集,那么自然会采用第一种实现,否则只能使用第二种实现了。
关于SpinLock的动作,可以看下面这张图:

机器相关的实现###
我们,知道与机器相关的实现利用了TAS指令集。那么什么是TAS呢?
TAS是 Test and Set的缩写。是一个原子操作。它修改内存的值,并返回原来的值。当一个进程P1对一个内存位置做TAS操作,不允许其它进程P2对此内存位置再做TAS操作。P2必须等P1操作完成后,再做TAS操作。因此,该操作被用来实现进程互斥。
有了这个概念,我们来看源代码。
代码在:
src/include/storage/s_lock.h
src/backend/storage/lmgr/s_lock.c
虽然说了对于SpinLock有两个底层实现,但是在上层调用时,我们是使用统一的接口的,接口在src/backend/storage/lmgr/s_lock.c中:
/*
* s_lock(lock) - platform-independent portion of waiting for a spinlock.
*/
int
s_lock(volatile slock_t *lock, const char *file, int line, const char *func)
{
...
while (TAS_SPIN(lock)) //调用点
{
...
}
可以发现这个TAS_SPIN(lock)是一个宏,
#define TAS_SPIN(lock) TAS(lock)
当使用基于TAS指令集的锁时,有:
#define TAS(lock) tas(lock)
对机器的TAS的使用在函数tas()中。
static __inline__ int
tas(volatile slock_t *lock)
{
register slock_t _res = 1;
/*
* Use a non-locking test before asserting the bus lock. Note that the
* extra test appears to be a small loss on some x86 platforms and a small
* win on others; it's by no means clear that we should keep it.
*
* When this was last tested, we didn't have separate TAS() and TAS_SPIN()
* macros. Nowadays it probably would be better to do a non-locking test
* in TAS_SPIN() but not in TAS(), like on x86_64, but no-one's done the
* testing to verify that. Without some empirical evidence, better to
* leave it alone.
*/
__asm__ __volatile__(
" cmpb $0,%1 \n"
" jne 1f \n"
" lock \n"
" xchgb %0,%1 \n"
"1: \n"
: "+q"(_res), "+m"(*lock)
: /* no inputs */
: "memory", "cc");
return (int) _res;
}
可以看到这段在C语言中的内嵌汇编代码即是调用了机器的TAS指令。假设lock原来的值为“0”,当P1去做申请lock时,能获取得到锁。而此时P2再去申请锁时,必须spin,因为此时lock的值已经被P1修改为“1”了。
用TAS来实现spin lock,此处要注意volatile的使用。volatile表示这个变量是易失的,所以会编译器会每次都去内存中取原始值,而不是直接拿寄存器中的值。
这避免了在多线程编程中,由于多个线程更新同一个变更,内存中和寄存器中值的不同步而导致变量的值错乱的问题。另外,也会影响编译器的优化行为。
具体汇编代码的解析,可以查看相关资料。
在使用时,PostgreSQL不直接调用tas()函数,而是通过:
int s_lock(volatile slock_t *lock, const char *file, int line, const char *func);
来申请spin lock。返回值是等待的时间。
机器无关的实现###
如果机器上没有TAS指令集,那么PostgreSQL利用PGSemaphores来实现SpinLock。
PGSemaphore是使用OS底层的semaphore来实现的,PG对其做了封装,提供了PG系统内部统一的semaphore操作接口。PG的用PGSemaphore结构体表示PG自身的semaphore信号,并将相关操作封装在sembuf中,传递给底层OS。
实现代码在:
src/backend/storage/lmgr/spin.c
我们知道这个TAS_SPIN(lock)是SpinLock的抽象定义:
#define TAS_SPIN(lock) TAS(lock)
在不使用TAS的场合,有:
#define TAS(lock) tas_sema(lock)
即调用tas_sema(lock)函数实现SpinLock:
int
tas_sema(volatile slock_t *lock)
{
/* Note that TAS macros return 0 if *success* */
return !PGSemaphoreTryLock(&SpinlockSemaArray[*lock]);
}
对于信号量,PostgreSQL分别针对POSIX 信号量、SYSTEM V信号量和windows信号量进行了不同的实现,实现代码分别在:
src/backend/port/posix_sema.c
src/backend/port/sysv_sema.c
src/backend/port/win32_sema.c
我们这里以SYSTEM V信号量为例进行讲解。
PGSemaphoreTryLock的定义为:
bool
PGSemaphoreTryLock(PGSemaphore sema)
{
int errStatus;
struct sembuf sops; //重要!!!
sops.sem_op = -1; /* decrement */
sops.sem_flg = IPC_NOWAIT; /* but don't block */
sops.sem_num = sema->semNum;
/*
* Note: if errStatus is -1 and errno == EINTR then it means we returned
* from the operation prematurely because we were sent a signal. So we
* try and lock the semaphore again.
*/
do
{
errStatus = semop(sema->semId, &sops, 1);
} while (errStatus < 0 && errno == EINTR);
...
即调用了PGSemaphores来实现SpinLock。
而PGSemaphores的定义为:
typedef struct PGSemaphoreData
{
int semId; /* semaphore set identifier */
int semNum; /* semaphore number within set */
} PGSemaphoreData;
在利用system V信号量时,我们有:
struct sembuf
{
unsigned short int sem_num; /* semaphore number */
short int sem_op; /* semaphore operation */
short int sem_flg; /* operation flag */
};
PGSemaphoreTryLock中的while循环里就是执行了semop操作。
而这些操作是OS自带的操作(在<sys/sem.h>头文件中):
extern int semop(int __semid, struct sembuf *opsptr, size_t nops);
很明显,此处PostgreSQL封装了OS底层的system V 的semaphore,然后利用OS底层的系统函数来操作。
剩下两种信号量大抵如此,此处不多言。
共通的操作###
SpinLock是分两种情况来分别实现的。这是它们的不同,在Spinlock之上有一些共通的操作要说明下。对于SpinLock的获取,并不是每次都成功,当尝试获取时发现一个对象已经被lock时,当前线程不会阻塞在改锁上,而是先spin(自旋)一定的次数之后再sleep一定的时间后尝试再次获取。对于每次spin之后的sleep时间,PostgreSQL使用了自适应算法,来决定spin的次数和每次spin后,sleep的时间。
下面两个变量要注意下:
spins_per_delay
该变量表示spin多少次后,开始sleep。默认为100,最大值为1000,最小值为10。
spins_per_delay的值基本上不变;但是cur_delay的值为当前值1倍和2倍之间变动。因此,spin delay次数越多,sleep时间会越长。
还有一个变量:
cur_delay
当前sleep的时间,最大值为1000,最小值为1。单位为ms。
小结###
本文讨论了关于PostgreSQL的SpinLock实现以及相关函数。SpinLock是PostgreSQL的最底层的锁,它的主要作用是为上层的锁提供支持。本文SpinLock就聊到这里,下次我们来聊PostGreSQL的LWLock和RegularLock。
注:本文还参考了这篇文章,在此表示感谢。
Postgres中的SpinLock锁的更多相关文章
- Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等
Java 中15种锁的介绍 Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等,在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类 ...
- 操作系统下spinlock锁解析、模拟及损耗分析
关于spinlock 我们在知道什么是spinlock之前,还需要知道为什么需要这个spinlock?spinlock本质就是锁,提到锁,我们就回到了多线程编程的混沌初期,为了实现多线程编程,操作系统 ...
- 浅谈Linux中的各种锁及其基本原理
本文首发于:https://mp.weixin.qq.com/s/Ahb4QOnxvb2RpCJ3o7RNwg 微信公众号:后端技术指南针 0.概述 通过本文将了解到如下内容: Linux系统的并行性 ...
- Linux中的各种锁及其基本原理
Linux中的各种锁及其基本原理 1.概述 通过本文将了解到如下内容: Linux系统的并行性特征 互斥和同步机制 Linux中常用锁的基本特性 互斥锁和条件变量 2.Linux的并行性特征 Linu ...
- pgsql中的行锁
pgsql中的行锁 前言 用户可见的锁 regular Lock 行级别 FOR UPDATE FOR NO KEY UPDATE FOR SHARE FOR KEY SHARE 测试下加锁之后的数据 ...
- Java中15种锁的介绍
作者:搜云库技术团队 原文:https://segmentfault.com/a/1190000017766364 1. Java 中15种锁的介绍 在读很多并发文章中,会提及各种各样锁如公平锁,乐观 ...
- 【C# 锁】 SpinLock锁 详细分析(包括内部代码)
OverView 同步基元分为用户模式和内核模式 用户模式:Iterlocked.Exchange(互锁).SpinLocked(自旋锁).易变构造(volatile关键字.volatile类.Thr ...
- postgres中几个复杂的sql语句
postgres中几个复杂的sql语句 需求一 需要获取一个问题列表,这个问题列表的排序方式是分为两个部分,第一部分是一个已有的数组[0,579489,579482,579453,561983,561 ...
- postgres中的中文分词zhparser
postgres中的中文分词zhparser postgres中的中文分词方法 基本查了下网络,postgres的中文分词大概有两种方法: Bamboo zhparser 其中的Bamboo安装和使用 ...
随机推荐
- 62. Unique Paths (Graph; DP)
A robot is located at the top-left corner of a m x n grid (marked 'Start' in the diagram below). The ...
- Python 关于数组矩阵变换函数numpy.nonzero(),numpy.multiply()用法
1.numpy.nonzero(condition),返回参数condition(为数组或者矩阵)中非0元素的索引所形成的ndarray数组,同时也可以返回condition中布尔值为True的值索引 ...
- 关于InvokeMethod Activity的异步调用
讨论地址:http://www.cnblogs.com/foundation/archive/2009/12/17/1626617.html 结论是IsCompleted的设置被忽略,看代码里注释 u ...
- [转]细说 ASP.NET Cache 及其高级用法
本文转自:http://www.cnblogs.com/fish-li/archive/2011/12/27/2304063.html 阅读目录 开始 Cache的基本用途 Cache的定义 Cach ...
- ZSTU4274 约素 2017-03-22 17:11 66人阅读 评论(0) 收藏
4274: 约素 Time Limit: 1 Sec Memory Limit: 128 MB Submit: 1807 Solved: 467 Description 判断一个正整数n的约数个数 ...
- 无线破解那点事(PJ)
有一段时间没有写博客了.只能说苦逼学生党伤不起啊,还好没挂-废话不说了,近期将会讲讲无线PJ那点事,也不是啥干货,就一些先前的笔记分享把. 0.无线网卡 想要提高破解效率,一块猛一点的USB无线网卡是 ...
- (2)-生成JSONObject的方法
生成JSONObject一般有两种方式,通过javabean或者map类型来生成.如下面的例子: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 2 ...
- 注册一个GitHub用户的过程
今天,我注册了一个GitHub用户.一开始,不知道GitHub是什么,还以为叫什么"getup",心里还想着什么网站名字这么奇怪,后来在舍友的帮助之下知道了原来是叫GitHub.下 ...
- threadpoolExecutor----自动执行任务
使用threadpoolExecutor,主要是任务的提交的执行和获取结果. 提交任务的方法有: 1.submit 2.execute 3.queue的add 其中1和2的使用必须是threadpoo ...
- [c# 20问] 2.如何转换XML文件
添加System.Xml引用 使用XmlReader转换字符串 DEMO #region Parse Xml private static void ParseXml(string xmlString ...