简介

在上一篇文章中的并发和ABA问题的介绍中,我们提到了要解决ABA中的memory reclamation问题,有一个办法就是使用RCU。

详见ABA问题的本质及其解决办法,今天本文将会深入的探讨一下RCU是什么,RCU和COW(Copy-On-Write)之间的关系。

RCU(Read-copy update)是一种同步机制,并在2002年被加入了Linux内核中。它的优点就是可以在更新的过程中,运行多个reader进行读操作。

熟悉锁的朋友应该知道,对于排它锁,同一时间只允许一个操作进行,不管这个操作是读还是写。

对于读写锁,可以允许同时读,但是不能允许同时写,并且这个写锁是排他的,也就是说写的同时是不允许进行读操作的。

RCU可以支持一个写操作和多个读操作同时进行。

更多精彩内容且看:

更多内容请访问www.flydean.com

Copy on Write和RCU

什么是Copy on Write? 它和read copy update有什么关系呢?

我们把Copy on Write简写为COW,COW是并发中经常会用到的一种算法,java里面就有java.util.concurrent.CopyOnWriteArrayList和java.util.concurrent.CopyOnWriteArraySet。

COW的本质就是,在并发的环境中,如果想要更新某个对象,首先将它拷贝一份,在这个拷贝的对象中进行修改,最后把指向原对象的指针指回更新好的对象。

CopyOnWriteArrayList和CopyOnWriteArraySet中的COW使用在遍历的时候。

我们知道使用Iterator来遍历集合的时候,是不允许在Iterator外部修改集合的数据的,只能在Iterator内部遍历的时候修改,否则会抛出ConcurrentModificationException。

而对于CopyOnWriteArrayList和CopyOnWriteArraySet来说,在创建Iterator的时候,就对原List进行了拷贝,Iterator的遍历是在拷贝过后的List中进行的,这时候如果其他的线程修改了原List对象,程序正常执行,不会抛出ConcurrentModificationException。

同时CopyOnWriteArrayList和CopyOnWriteArraySet中的Iterator是不支持remove,set,add方法的,因为这是拷贝过来的对象,在遍历过后是要被丢弃的。在它上面的修改是没有任何意义的。

在并发情况下,COW其实还有一个问题没有处理,那就是对于拷贝出来的对象什么时候回收的问题,是不是可以马上将对象回收?有没有其他的线程在访问这个对象? 处理这个问题就需要用到对象生命周期的跟踪技术,也就是RCU中的RCU-sync。

所以RCU和COW的关系就是:RCU是由RCU-sync和COW两部分组成的。

因为java中有自动垃圾回收功能,我们并不需要考虑拷贝对象的生命周期问题,所以在java中我们一般只看到COW,看不到RCU。

RCU的流程和API

我们将RCU和排它锁和读写锁进行比较。

对于排它锁来说,需要这两个API:

lock()
unlock()

对于对写锁来说,需要这四个API:

read_lock()
read_unlock()
write_lock()
write_unlock()

而RCU需要下面三个API:

rcu_read_lock()
rcu_read_unlock()
synchronize_rcu()

rcu_read_lock和rcu_read_unlock必须是成对出现的,并且synchronize_rcu不能出现在rcu_read_lock和rcu_read_unlock之间。

虽然RCU并不提供任何排他锁,但是RCU必须要满足下面的两个条件:

  1. 如果Thread1(T1)中synchronize_rcu方法在Thread2(T2)的rcu_read_lock方法之前返回,则happens before synchronize_rcu的操作一定在T2的rcu_read_lock方法之后可见。
  2. 如果T2的rcu_read_lock方法调用在T1的synchronize_rcu方法调用之前,则happens after synchronize_rcu的操作一定在T2的rcu_read_unlock方法之前不可见。

听起来很拗口,没关系,我们画个图来理解一下:

记住RCU比较的是synchronize_rcu和rcu_read_lock的顺序。

Thread2和Thread3中rcu_read_lock在synchronize_rcu之前执行,则b=2在T2,T3中一定不可见。

Thread4中rcu_read_lock虽然在synchronize_rcu启动之后才开始执行的,但是rcu_read_unlock是在synchronize_rcu返回之后才执行的,所以可以等同于看做Thread5的情况。

Thread5中,rcu_read_lock在synchronize_rcu返回之后才执行的,所以a=1一定可见。

RCU要注意的事项

RCU虽然没有提供锁的机制,但允许同时多个线程进行读操作。注意,RCU同时只允许一个synchronize_rcu操作,所以需要我们自己来实现synchronize_rcu的排它锁操作。

所以对于RCU来说,它是一个写多个读的同步机制,而不是多个写多个读的同步机制。

RCU的java实现

最后放上一段大神的RCU的java实现代码:

public class RCU {
final static long NOT_READING = Long.MAX_VALUE;
final static int MAX_THREADS = 128;
final AtomicLong reclaimerVersion = new AtomicLong(0);
final AtomicLongArray readersVersion = new AtomicLongArray(MAX_THREADS); public RCU() {
for (int i=0; i < MAX_THREADS; i++) readersVersion.set(i, NOT_READING);
} public static int getTID() {
return (int)(Thread.currentThread().getId() % MAX_THREADS);
} public void read_lock(final int tid) { // rcu_read_lock()
final long rv = reclaimerVersion.get();
readersVersion.set(tid, rv);
final long nrv = reclaimerVersion.get();
if (rv != nrv) readersVersion.lazySet(tid, nrv);
} public void read_unlock(final int tid) { // rcu_read_unlock()
readersVersion.set(tid, NOT_READING);
} public void synchronize_rcu() {
final long waitForVersion = reclaimerVersion.incrementAndGet();
for (int i=0; i < MAX_THREADS; i++) {
while (readersVersion.get(i) < waitForVersion) { } // spin
}
}
}

简单讲解一下这个RCU的实现:

readersVersion是一个长度为128的Long数组,里面存放着每个reader的读数。默认情况下reader存储的值是NOT_READING,表示未存储任何数据。

在RCU初始化的时候,将会初始化这些reader。

read_unlock方法会将reader的值重置为NOT_READING。

reclaimerVersion存储的是修改的数据,它的值将会在synchronize_rcu方法中进行更新。

同时synchronize_rcu将会遍历所有的reader,只有当所有的reader都读取完毕才继续执行。

最后,read_lock方法将会读取reclaimerVersion的值。这里会读取两次,如果两次的结果不同,则会调用readersVersion.lazySet方法,延迟设置reader的值。

为什么要读取两次呢?因为虽然reclaimerVersion和readersVersion都是原子性操作,但是在多线程环境中,并不能保证reclaimerVersion一定就在readersVersion之前执行,所以我们需要添加一个内存屏障:memory barrier来实现这个功能。

总结

本文介绍了RCU算法和应用。希望大家能够喜欢。

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/concurrent-read-copy-updatercu/

本文来源:flydean的博客

欢迎关注我的公众号:程序那些事,更多精彩等着您!

并发和Read-copy update(RCU)的更多相关文章

  1. Linux内核RCU(Read Copy Update)锁简析

    在非常早曾经,大概是2009年的时候.写过一篇关于Linux RCU锁的文章<RCU锁在linux内核的演变>,如今我承认.那个时候我尽管懂了RCU锁,可是我没有能力用一种非常easy的描 ...

  2. Kernel boot options

    There are three ways to pass options to the kernel and thus control its behavior: When building the ...

  3. linux 并发 RCU

    What is RCU, Fundamentally? https://lwn.net/Articles/262464/ If you can fill the unforgiving secondw ...

  4. java高并发系列 - 第2天:并发级别

    由于临界区的存在,多线程之间的并发必须受到控制.根据控制并发的策略,我们可以把并发的级别分为阻塞.无饥饿.无障碍.无锁.无等待几种. 阻塞 一个线程是阻塞的,那么在其他线程释放资源之前,当前线程无法继 ...

  5. Linux中的RCU的那点事

      原文:https://zhuanlan.zhihu.com/p/67520807     今天来讲一下这Linux内核中的RCU(Read Copy Update,读复制更新)机制. 我主要参考的 ...

  6. Java高并发系列——检视阅读

    Java高并发系列--检视阅读 参考 java高并发系列 liaoxuefeng Java教程 CompletableFuture AQS原理没讲,需要找资料补充. JUC中常见的集合原来没讲,比如C ...

  7. 从自旋锁、睡眠锁、读写锁到 Linux RCU 机制讲解

    ​    同步自我的 csdn 博客 6.S081 从自旋锁.睡眠锁.读写锁到 Linux RCU 机制讲解_我说我谁呢 --CSDN博客 总结一下 O/S 课程里面和锁相关的内容. 本文是 6.S0 ...

  8. 深入理解 Linux 的 RCU 机制

    欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~ 作者:梁康 RCU(Read-Copy Update),是 Linux 中比较重要的一种同步机制.顾名思义就是"读,拷贝更新&quo ...

  9. Linux内核同步:RCU

    linux内核 RCU机制详解 简介 RCU(Read-Copy Update)是数据同步的一种方式,在当前的Linux内核中发挥着重要的作用.RCU主要针对的数据对象是链表,目的是提高遍历读取数据的 ...

  10. RCU介绍

    RCU原理: RCU(Read-Copy Update),顾名思义就是读-拷贝修改,它是基于其原理命名的.对于被RCU保护的共享数据结构,读者不需要获得任何锁就可以访问它,但写者在访问它时首先拷贝一个 ...

随机推荐

  1. JS内存爆破问题

    原理 检测到调试,格式化等,疯狂的在js文件,或者html中进行读写,cookie重写追加,字节追加,导致内存不足够,卡死 内存爆破,指js通过死循环/频繁操作数据库(包括cookie)/频繁调取hi ...

  2. 在Windows环境中配置使用我们搭建的DNS服务器

    1.修改网卡的设置,首选DNS用我们自己的 2.在命令行中测试 专业的nslookup 3.已知的问题 每次在DNS服务器的web界面中,修改了解析,必须用docker restart dns命令,把 ...

  3. 数据结构(三):舞伴配对问题(C++,队列)

    好家伙, 题目如下: 1.舞伴配对问题:假设在周末舞会上,男士们和女士们进入舞厅时,各自排成一队.跳舞开始时,依次从男队和女队的队头上各出一人配成舞伴. 2.若两队初始人数不相同,则较长的那一队中未配 ...

  4. 【Azure App Service】当App Service中使用系统标识无法获取Access Token时

    问题描述 App Serive上的应用配置了系统标识(System Identity),通过系统标识获取到访问Key Vault资源的Access Token.但这次确遇见了无法获取到正常的Acces ...

  5. 【Azure 应用服务】在安全漏洞扫描中发现有泄露服务器IIS版本的情况,如何实现屏蔽服务版本号信息呢?

    问题描述 当对Azure App Service应用进行安全扫描时,发现了HTTP/S请求的响应头中会包含服务端IIS的版本信息,这是一个低风险因素. 如: Server: Microsoft-IIS ...

  6. 【Azure 应用服务】部署Azure Web App时,是否可以替换hostingstart.html文件呢?

    问题描述 当成功创建一个Web App时,通过高级工具(Kudu)可以查看 Web App的根目录(wwwroot)中有一个默认的文件(hostingstart.html).它就是应用服务的默认页面. ...

  7. 【Azure 环境】Azure Key Vault 采用自签名证书,是否需要CA provider

    关于 Azure Key Vault 证书,密钥保管库证书支持适用于 x509 证书管理,它提供以下行为: 允许证书所有者通过密钥保管库创建过程或通过导入现有证书来创建证书. 包括自签名证书和证书颁发 ...

  8. 一文了解 Nebula Graph DBaaS 服务——Nebula Graph Cloud Service

    Nebula Graph DBaaS 作为一款 DBaaS(DataBase as s Service)的产品,Nebula Graph Cloud Service 极大地降低了研发人员使用 Nebu ...

  9. [C++逆向] 7 变量在内存中的位置和访问方式

    目录 全局变量和局部变量的区别 局部静态变量 有意思的 堆变量 变量类型 作用域 可访问 全局变量 进程作用域 整个进程可访问 静态变量 文件作用域 当前代码文件可访问 局部变量 函数作用域 函数内可 ...

  10. C++ //类模板对象做函数参数 //三种方式 //1.指定传入的类型 --直接显示对象的数据类型 //2.参数模板化 --将对象中的参数变为模板进行传递 //3.整个类模板化 --将这个对象类型 模板化进行传递

    1 //类模板对象做函数参数 2 //三种方式 3 //1.指定传入的类型 --直接显示对象的数据类型 4 //2.参数模板化 --将对象中的参数变为模板进行传递 5 //3.整个类模板化 --将这个 ...