Nginx原子操作及自旋锁实现

简介: Nginx原子操作及自旋锁实现

Nginx原子操作


执行原子操作的变量只有整形

image.png

这两种整型都使用了volatile关键字告诉C编译器不要做优化

nginx原子操作提供的2个方法

  • ngx_atomic_cmp_set
入参:
ngx_auomic_t*lock,
ngx_atomic_unit_t old,
ngx_atomic_unit set



image.png

compare & set
  • ngx_atomic_fetch_add
入参:
ngx_atomic_t*value,
ngx_atomic_int_t add


image.png


x86的SMP多核架构下的原子操作


当无法实现原子操作时
就只能用volatile 关键字在C语言级别上模拟原子操作
目前绝大多数体系架构都是支持原子操作的

image.png

Nginx在源代码中实现对整型的原子操作
需通过内联汇编语言直接操作硬件才能做到

使用GCC编译器在C语言中嵌入汇编语言的方式使用__asm__关键字

__asm__volatile(汇编语句部分)
:输出部分
:输入部分
:破坏描述部分
volatile关键字用于限制GCC编译器对这段代码做优化


GCC如何内联汇编语言


汇编语句

引号中所包含的汇编语句可以直接用占位符%来引用C语言中的变量 
(最多10个,%0~%9)
介绍个汇编语句
  • cmpxchgl r,[m]

image.png

首先会用m比较eax寄存器中的值
如果相等 则把m的值设为r,zf标志位设为1
否则将zf标志位设为0,寄存器中的值设为m


输出部分

将寄存器中的值设置为C语言变量中


输入部分

将C语言变量设置到寄存器中


破坏描述部分

通知编译器使用了哪些寄存器、内存


ngx_atomic_cmp_set


image.png

从内存中获取lock变量(不使用寄存器)
把old变量写入eax寄存器
把set变量写入通用寄存器
首先锁住总线防止多核的并发执行
接着判断原子变量old和old值是否相等
若相等 则把lock值设为set 同时res为1
若不相等 则设res为0


自旋锁


每当内核调度到这个进程执行时
就持续检查是否可以获得到锁
在拿不到锁时
这个进程的代码将会一直在自旋锁代码处执行
直到其他进程释放了锁且当前进程获得到锁
代码才会继续向下执行

适用场景

自旋锁主要为多处理器操作系统而设置
解决的共享资源保护场景是进程使用锁的时间非常短
(如果太久 会占用大量的CPU资源)
如果使用锁的进程不希望自己进入睡眠状态
特别它处理的是非常核心的事件时
这时应该使用自旋锁
大部分情况下Nginx的worker进程最好都不要进入睡眠状态
因为它非常繁忙
这个进程的epoll上可能会有十万甚至百万的TCP连接等待着处理
进程一旦睡眠后必须等待其他时间的唤醒
这中间极其频繁的进程间切换带来的负载消耗可能无法让用户接受

注意

自选锁对于单处理器操作系统来说一样有效
不进入睡眠状态并不意味着其他可执行状态的进程得不到执行
Linux内核中对于每个处理器都有一个运行队列
自选锁可以仅仅调整当前进程在运行队列中的顺序
或者调整进程的时间片
这都会为当前处理器上的其他进程提供被调度的机会
以使得锁被其他进程释放

Nginx基于原子操作的自旋锁ngx_spinlock的实现

入参:
a、
lock是原子变量表达的锁
值为0表示锁是被释放的
值不为0表示锁已经被某个进程持有了
b、value参数表示希望当锁没有被任何进程持有时
(也就是lock值为0)把lock值设为value表示当前进程持有了
c、spin参数表示在多处理器系统内
当ngx_spinlock方法没有拿到锁时
当前进程在内核的一次调度中
该方法等待其他处理器释放锁的时间

ngx_spinlock实现逻辑

image.png

不要立刻让出CPU

在多处理器下
更好的做法是当前进程不要立刻让出正在使用的CPU处理器
而是等待一段时间
看看其他处理器上的进程是否会释放锁
这会减少进程间切换的次数

检查lock是否释放的频率越来越小

随着等待的次数越来越多
实际去检查lock是否释放的频率会越来越小
因为检查lock值会更消耗CPU
而执行ngx_cpu_pause对于CPU能耗很节省的

nginx_cpu_pause

nginx_cpu_pause是在许多架构体系中专门为了自选锁而提供的命令
它会告诉CPU现在处于自选锁等待状态
通常一些CPU会将自己置于节能状态 降低功耗
"注意"在执行ngx_cpu_pause后
当前进程没有让出正使用的处理器

ngx_sched_yield

当前进程仍然处于可执行状态
但暂时让出处理器
使得处理器优先调度其他可执行状态的进程
在进程被内核再次调度时 
在for循环代码中可以期望其他进程释放锁
相关文章
9kr
|
应用服务中间件 网络安全 nginx
通过宝塔Nginx反代HomeAssistant并添加SSL实现隐藏端口号与域名访问
HomeAssistant默认使用8123端口,带端口访问既不美观也不方便。 通过宝塔Nginx默认反代配置会出现各种意外错误,本文将通过修改HomeAssistant与反代配置解决该问题。
9kr
3942 1
通过宝塔Nginx反代HomeAssistant并添加SSL实现隐藏端口号与域名访问
|
缓存 负载均衡 算法
Nginx实现负载均衡(整合SpringBoot小demo)
Nginx实现负载均衡(整合SpringBoot小demo)
381 4
Nginx实现负载均衡(整合SpringBoot小demo)
|
存储 应用服务中间件 Linux
FastDFS+Nginx实现文件服务器
FastDFS+Nginx实现文件服务器
FastDFS+Nginx实现文件服务器
|
存储 Java 应用服务中间件
线程池设计, 从简单的我们平常设计线程池图解,到生活中的类似线程池的处理现实场景, 到简单的C++模拟nginx写的单链表组织工作队列的简单线程池实现 + nginx 部分源码刨析
线程池设计, 从简单的我们平常设计线程池图解,到生活中的类似线程池的处理现实场景, 到简单的C++模拟nginx写的单链表组织工作队列的简单线程池实现 + nginx 部分源码刨析
线程池设计, 从简单的我们平常设计线程池图解,到生活中的类似线程池的处理现实场景, 到简单的C++模拟nginx写的单链表组织工作队列的简单线程池实现 + nginx 部分源码刨析
|
存储 网络协议 Java
内存池组件以及根据nginx内存池源码设计实现简易内存池
内存池组件以及根据nginx内存池源码设计实现简易内存池
内存池组件以及根据nginx内存池源码设计实现简易内存池
|
SQL 负载均衡 应用服务中间件
nginx安装 、分发实现多域名访问同一主机不同端口
nginx安装 、分发实现多域名访问同一主机不同端口
962 0
nginx安装 、分发实现多域名访问同一主机不同端口
|
监控 数据可视化 应用服务中间件
Nginx系列:用GoAccess实现可视化并实时监控access日志
Nginx系列:用GoAccess实现可视化并实时监控access日志
372 0
Nginx系列:用GoAccess实现可视化并实时监控access日志
|
域名解析 JavaScript 前端开发
Linux安装NodeJs并配合Nginx实现反向代理
Linux安装NodeJs并配合Nginx实现反向代理
381 0
|
负载均衡 网络协议 应用服务中间件
nginx实现负载均衡
nginx实现负载均衡
342 0
nginx实现负载均衡
|
域名解析 网络协议 安全
nginx配置ssl证书实现https
nginx配置ssl证书实现https
488 0
nginx配置ssl证书实现https