kotlin協程2:協程間數據同步(多線程間協程的數據同步)

  1. 協程間的數據傳遞:單值傳遞和多值傳遞(數據流的傳遞)
    • 單值傳遞:
      • launch創建的協程:
        • 多協程是順序執行,且各個協程的函數體內沒有掛起當前協程的邏輯(包括延時邏輯),各個協程的執行順序是順序執行,則下一個協程可以使用上一個協程的返回值。
        • 根協程聲明值,子協程修改值,或者延遲產生數據實現在協程之間傳遞值,不過需要注意的是分析根協程和各個協程的邏輯的執行順序,對數據的修改順序會影響到數據的狀態進而也就影響了協程對數據的使用。
      • async創建的協程:
        • 協程并發,此時不建議協程間數據傳遞使用,個人理解的此時的使用場景是協程創建自己的數據而所有協程執行完成匯總協程產生的數據進行使用,使用其并發縮短了邏輯的執行時間。
      • 數據流的傳遞:
        • 通過通道(channel)進行協程間數據流的傳遞。
          • 阻塞隊列(BlockingQueue):項目中常見的模型(生產者消費者模型)常用的數據結構,提供put和take且都是阻塞的,即take的時候若沒有數據則會阻塞線程直到有了數據,同樣put也是沒有空間則阻塞線程等到數據take隊列有了空間。
          • 協程中的通道等同于阻塞隊列,用于協程間數據流的傳遞,提供send(數據的發送)和receive(數據的接受)和隊列不同的是不會阻塞。


            通道數據傳遞
            • 和隊列不同,通道提供了close關閉邏輯,即通道關閉后則不會再有數據發送,close操作類似于向通道發送一個特殊的關閉標記. 收到這個關閉標記之后, 對通道的迭代操作將會立即停止, 因此可以保證在關閉操作以前發送的所有數據都會被正確接收。


              通道關閉
            • 構造通道的生產者(producer):在協程中產生一個數值序列, 這是很常見的模式. 這是并發代碼中經常出現的 生產者(producer)/消費者(consumer) 模式的一部分. 你可以將生產者抽象為一個函數, 并將通道作為函數的參數, 然后向通道發送你生產出來的值, 但這就違反了通常的函數設計原則, 也就是函數的結果應該以返回值的形式對外提供.
              通道Produce
              備注:如圖所示,produce是CoroutineScope提供的一個擴展函數,參數為上下文,通道緩沖區大小和產生序列數的lambda表達式,封裝了通道的創建及其上下文環境,可以讓用戶專注于生產數序列即可。
              有一個便利的協程構建器, 名為 produce, 它可以很簡單地編寫出生產者端的正確代碼, 還有一個擴展函數 consumeEach, 可以在消費者端代碼中替代 for 循環:即:
              通道produce封裝及其函數consumeEach
              • 帶緩沖區的通道:
                • 通道分為發送和接受兩個場景,通常的通道如果沒有接受僅有發送的場景則是將發送協程掛起,等待接受協程的調用時才會執行發送的邏輯進行數據的發送,同樣 先執行接受協程也會將接受協程掛起等待發送協程的調用然后重新執行接受協程。
              • 但是對于帶緩沖區的通道則是在數據的發送提供一個緩沖區,即通道的參數指定發送數據的緩存個數,此時通道沒有接受的場景,在僅有的發送場景的時候也會發送緩沖區的數據,達到緩沖區的指定數后將發送數據協程掛起。
              • Channel() 工廠函數和 produce 構建器都可以接受一個可選的 capacity 參數, 用來指定 緩沖區大小. 緩沖區可以允許發送者在掛起之前發送多個數據, 類似于指定了容量的 BlockingQueue, 它會在緩沖區已滿的時候發生阻塞.
                緩沖區通道
            • 定時器通道:類似于timer定時任務,即根據指定的定時時間發送一個unit的定時值,用戶可以根據定時值處理一些常見的定時邏輯,可以使用 ticker 工廠函數來創建這種通道. 使用通道的 ReceiveChannel.cancel 方法來指出不再需要它繼續產生數據了。即:


              定時器通道
      • 多協程訪問通道:
        • 多協程接受通道的數據:
          • 通道里的數據可以被多個協程收到,通道的數據的發送不會發生變化,接受數據的協程不固定
          • 取消通道的生成者協程,則通道會被關閉,進而接受通道數據的協程也都會被取消。
          • 在接受通道數據的協程的函數體中for循環和consumeEach的不同點是:前面for循環若出現異常并不會取消接受數據的協程因為豈不是掛起函數,也就不會取消其他的接受通道數據的協程,但是后者是一個掛起函數若出現異常則會取消協程,進而影響到通道的關閉,其他接受者協程和生產者協程也就取消。


            生產者協程

            處理數據協程

            啟動多個數據的協程
        • 多協程向通道發送數據:
          • 發射數據也可以多個協程向其寫入數據,數據的順序取決于協程的寫入邏輯。
          • 取消所有的協程則通道的數據也就隨之取消。


            通道數據生產者

            多協程寫入數據
        • 如果從多個協程中調用通道的發送和接收操作, 從調用發生的順序來看, 這些操作是 平等的. 通道對這些方法以先進先出(first-in first-out)的順序進行服務, 也就是說, 第一個調用 receive 的協程會得到通道中的數據. 在下面的示例程序中, 有兩個 "ping" 和 "pong" 協程, 從公用的一個 "table" 通道接收 "ball" 對象.


          多協程訪問通道的順序
      • 管道:上面講了多協程生產數據和多協程接收數據,在這個場景中可以講這些協程串聯起來形成一個數據處理的管道。
      • ReceiveChannel:通道的數據接受的封裝對象,producer產生數據后返回的對象即時這個對象,封裝了產生的數據(具體可以參考上面的代碼)。
      • 管道的協程串聯即是通過上面的這個對象進行串聯,即中間協程可以接受生產者產生的數據修改后再封裝返回,最終到最后一個協程通過其接受者獲取到數據。即:


        管道生產者

        管道數據中間處理

        管道的串聯
  2. 多線程協程的數據的同步:
    問題:
    • 通過Default線程指定,多協程的執行會在多線程中執行,此時就會出現多線程共享值的問題,即多個線程同時訪問并修改同一個值就會出現意想不到的問題。


      共享值

      多線程

      備注:多線程中多協程的原因導致并沒有出現100*1000的結果值。

  • 多協程的多線程執行的解決和多線程共享值基本一樣,即:
    • 針對簡單的基礎變量,使用volatile(即使用其原子性:針對變量的每一個線程的讀和寫保證其可見性)不能保證并發問題解決,對于上面的案例使用這個關鍵字并不能保證每次都是100*1000,即:


      并發案例
    • 下面幾種方法可以保證多線程對共享值的修改的線程安全。
      • 和多線程處理一致數據結構使用線程安全的數據結構,即使用線程安全的 (也叫 同步的(synchronized), 線性的(linearizable), 或者 原子化的(atomic)) 數據結構, 這些數據結構會對需要在共享的狀態數據上進行的操作提供必要的同步保障. 在我們的簡單的計數器示例中, 可以使用 AtomicInteger 類, 它有一個原子化的 incrementAndGet 操作且對于這個具體的問題, 這是最快的解決方案. 這種方案適用于計數器, 集合, 隊列, 以及其他標準數據結構, 以及這些數據結構的基本操作. 但是, 這種方案并不能簡單地應用于復雜的狀態變量, 或者那些沒有現成的線程安全實現的復雜操作.


        線程安全的數據結構
      • 細粒度的線程限定:將涉及到的共享值轉變的lambda表達式或者匿名函數放到獨一線程中去,即對于共享值的修改放到唯一線程中去修改。
        • 缺點是:代碼的執行變慢了,因為在執行中要不停的進行線程的切換,即協程線程和修改數據的線程來回切換。


          單線程
        • 粗粒度的線程限定:即將整個操作放到一個獨立線程中去這樣比細粒度線程限定速度快又能保證共享值的線程安全。


          單線程線程安全
          • 加鎖同步即和多線程中的鎖同步一致,不過協程的語法和線程不一致,協程是通過Mutex實現的它的 lock和 unlock 函數可以用來界定臨界區. 主要的區別在于 Mutex.lock() 是一個掛起函數. 它不會阻塞線程.
            * 還有一個擴展函數 withLock, 它用非常便利的方式實現 mutex.lock(); try { ... } finally { mutex.unlock() } 模式:
            加鎖實現同步
  1. select選擇:上面通道中介紹了多個掛起函數發送值的場景,使用select選擇語法可以在多個掛起函數中選擇第一個執行完畢的結果,其他掛起函數取消或者關閉。
    • 通道中選擇使用:


      掛起函數1

      掛起函數2

      通道選擇掛起函數

      測試代碼及其結果
    • 通道的關閉會導致通道的select選擇的onReceive函數語句失敗且拋出對應的異常,對于此可以使用函數onReceiveCatching對其進行抓取并處理其異常。即:


      選擇處理異常掛起函數

      生產者掛起函數及其測試掛起函數和結果

      備注:通道上選擇的優先級:

      • 多個通道且每個通道的數據發送有先后順序:此時取決于數據的發送順序,例如上面的第一個案例,第一個通道500ms發送一個數據所以第一個數據是第一個通道,且第一個通道的數據居多,偶爾第二個通道也會有數據。
      • 多個通道且每個通道沒有具體的數據發送順序區分:比如第二個案例:優先使用第一個通道,后續會根據通道的數據發送順序獲取數據。
    • 通道的數據發送時選擇發送的通道:上面介紹了在接受數據的時候多通道的選擇,此處介紹的是發送數據發送到多個通道:選擇表達式也可以使用 onSend 子句, 它可以與選擇表達式的偏向性結合起來, 起到很好的作用。

參考文章:
協程間的通信
協程與線程間的關系
協程的并發問題

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容