日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

并发和Read-copy update(RCU)

發布時間:2024/2/28 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 并发和Read-copy update(RCU) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 簡介
  • Copy on Write和RCU
  • RCU的流程和API
  • RCU要注意的事項
  • RCU的java實現
  • 總結

簡介

在上一篇文章中的并發和ABA問題的介紹中,我們提到了要解決ABA中的memory reclamation問題,有一個辦法就是使用RCU。

詳見ABA問題的本質及其解決辦法,今天本文將會深入的探討一下RCU是什么,RCU和COW(Copy-On-Write)之間的關系。

RCU(Read-copy update)是一種同步機制,并在2002年被加入了Linux內核中。它的優點就是可以在更新的過程中,運行多個reader進行讀操作。

熟悉鎖的朋友應該知道,對于排它鎖,同一時間只允許一個操作進行,不管這個操作是讀還是寫。

對于讀寫鎖,可以允許同時讀,但是不能允許同時寫,并且這個寫鎖是排他的,也就是說寫的同時是不允許進行讀操作的。

RCU可以支持一個寫操作和多個讀操作同時進行。

更多精彩內容且看:

  • 區塊鏈從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特幣等持續更新
  • Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新
  • Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新
  • java程序員從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程

更多內容請訪問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必須要滿足下面的兩個條件:

  • 如果Thread1(T1)中synchronize_rcu方法在Thread2(T2)的rcu_read_lock方法之前返回,則happens before synchronize_rcu的操作一定在T2的rcu_read_lock方法之后可見。
  • 如果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)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。