剛剛,阿里開源 iOS 協程開發框架 coobjc!

阿里技術_發表於2019-02-28

640?wx_fmt=png


阿里妹導讀:剛剛,阿里巴巴正式對外開源了基於 Apache 2.0 協議的協程開發框架 coobjc,開發者們可以在 Github 上自主下載。


coobjc是為iOS平臺打造的開源協程開發框架,支援Objective-C和Swift,同時提供了cokit庫為Foundation和UIKit中的部分API提供了協程化支援,本文將為大家詳細介紹coobjc的設計理念及核心優勢。



開源地址


https://github.com/alibaba/coobjc



iOS非同步程式設計問題


從2008年第一個iOS版本釋出至今的11年時間裡,iOS的非同步程式設計方式發展緩慢。


640?wx_fmt=png


基於 Block 的非同步程式設計回撥是目前 iOS 使用最廣泛的非同步程式設計方式,iOS 系統提供的 GCD 庫讓非同步開發變得很簡單方便,但是基於這種程式設計方式的缺點也有很多,主要有以下幾點:


  • 容易進入"巢狀地獄"

  • 錯誤處理複雜和冗長

  • 容易忘記呼叫 completion handler

  • 條件執行變得很困難

  • 從互相獨立的呼叫中組合返回結果變得極其困難

  • 在錯誤的執行緒中繼續執行(如子執行緒操作UI)

  • 難以定位原因的多執行緒崩潰(手淘中多執行緒crash已佔比60%以上)

  • 鎖和訊號量濫用帶來的卡頓、卡死


針對多執行緒以及尤其引發的各種崩潰和效能問題,我們制定了很多程式設計規範、進行了各種新人培訓,嘗試降低問題發生的概率,但是問題依然很嚴峻,多執行緒引發的問題佔比並沒有明顯的下降,非同步程式設計本來就是很複雜的事情,單靠規範和培訓是難以從根本上解決問題的,需要有更加好的程式設計方式來解決。


解決方案


上述問題在很多系統和語言開發中都可能會碰到,解決問題的標準方式就是使用協程,C#、Kotlin、Python、Javascript 等熱門語言均支援協程極其相關語法,使用這些語言的開發者可以很方便的使用協程及相關功能進行非同步程式設計。


2017 年的 C++ 標準開始支援協程,Swift5 中也包含了協程相關的標準,從現在的發展趨勢看基於協程的全新的非同步程式設計方式,是我們解決現有非同步程式設計問題的有效的方式,但是蘋果基本已經不會升級 Objective-C 了,因此使用Objective-C的開發者是無法使用官方的協程能力的,而最新 Swift 的釋出和推廣也還需要時日,為了讓廣大iOS開發者能快速享受到協程帶來的程式設計方式上的改變,手機淘寶架構團隊基於長期對系統底層庫和彙編的研究,通過彙編和C語言實現了支援 Objective-C 和 Swift 協程的完美解決方案 —— coobjc。


核心能力


  • 提供了類似C#和Javascript語言中的Async/Await程式設計方式支援,在協程中通過呼叫await方法即可同步得到非同步方法的執行結果,非常適合IO、網路等非同步耗時呼叫的同步順序執行改造。

  • 提供了類似Kotlin中的Generator功能,用於懶計算生成序列化資料,非常適合多執行緒可中斷的序列化資料生成和訪問。

  • 提供了Actor Model的實現,基於Actor Model,開發者可以開發出更加執行緒安全的模組,避免由於直接函式呼叫引發的各種多執行緒崩潰問題。

  • 提供了元組的支援,通過元組Objective-C開發者可以享受到類似Python語言中多值返回的好處。


內建系統擴充套件庫


  • 提供了對NSArray、NSDictionary等容器庫的協程化擴充套件,用於解決序列化和反序列化過程中的非同步呼叫問題。

  • 提供了對NSData、NSString、UIImage等資料物件的協程化擴充套件,用於解決讀寫IO過程中的非同步呼叫問題。

  • 提供了對NSURLConnection和NSURLSession的協程化擴充套件,用於解決網路非同步請求過程中的非同步呼叫問題。

  • 提供了對NSKeyedArchieve、NSJSONSerialization等解析庫的擴充套件,用於解決解析過程中的非同步呼叫問題。


coobjc設計


640?wx_fmt=png


  • 最底層是協程核心,包含了棧切換的管理、協程排程器的實現、協程間通訊channel的實現等。

  • 中間層是基於協程的操作符的包裝,目前支援async/await、Generator、Actor等程式設計模型。

  • 最上層是對系統庫的協程化擴充套件,目前基本上覆蓋了Foundation和UIKit的所有IO和耗時方法。


核心實現原理


協程的核心思想是控制呼叫棧的主動讓出和恢復。一般的協程實現都會提供兩個重要的操作:


  • Yield:是讓出cpu的意思,它會中斷當前的執行,回到上一次Resume的地方。

  • Resume:繼續協程的執行。執行Resume後,回到上一次協程Yield的地方。


我們基於執行緒的程式碼執行時候,是沒法做出暫停操作的,我們現在要做的事情就是要程式碼執行能夠暫停,還能夠再恢復。 基本上程式碼執行都是一種基於呼叫棧的模型,所以如果我們能把當前呼叫棧上的狀態都儲存下來,然後再能從快取中恢復,那我們就能夠實現yield和 resume。


實現這樣操作有幾種方法呢?


  • 第一種:利用glibc 的 ucontext元件(雲風的庫)。

  • 第二種:使用匯編程式碼來切換上下文(實現c協程),原理同ucontext。

  • 第三種:利用C語言語法switch-case的奇淫技巧來實現(Protothreads)。

  • 第四種:利用了 C 語言的 setjmp 和 longjmp。

  • 第五種:利用編譯器支援語法糖。


上述第三種和第四種只是能過做到跳轉,但是沒法儲存呼叫棧上的狀態,看起來基本上不能算是實現了協程,只能算做做demo,第五種除非官方支援,否則自行改寫編譯器通用性很差。而第一種方案的 ucontext 在iOS上是廢棄了的,不能使用。那麼我們使用的是第二種方案,自己用匯編模擬一下 ucontext。


模擬ucontext的核心是通過getContext和setContext實現儲存和恢復呼叫棧。需要熟悉不同CPU架構下的呼叫約定(Calling Convention).  彙編實現就是要針對不同cpu實現一套,我們目前實現了 armv7、arm64、i386、x86_64,支援iPhone真機和模擬器。


Show me the code


說了這麼多,還是看看程式碼吧,我們從一個簡單的網路請求載入圖片功能來看看coobjc到底是如何使用的。


下面是最普通的網路請求的寫法:


640?wx_fmt=jpeg


下面是使用coobjc庫協程化改造後的程式碼:


640?wx_fmt=jpeg


原本需要20行的程式碼,通過coobjc協程化改造後,減少了一半,整個程式碼邏輯和可讀性都更加好,這就是coobjc強大的能力,能把原本很複雜的非同步程式碼,通過協程化改造,轉變成邏輯簡潔的順序呼叫。


coobjc還有很多其他強大的能力,本文對於coobjc的實際使用就不過多介紹了,感興趣的朋友可以去官方github倉庫自行下載檢視。


效能提升


我們在iPhone7 iOS11.4.1的裝置上使用協程和傳統多執行緒方式分別模擬高併發讀取資料的場景,下面是兩種方式得到的壓測資料。


  • 測試機器:iPhone7 iOS11.4.1

  • 資料檔案大小:20M

  • 協程最多使用執行緒數:4

  • 資料測試結果(統計的是所有併發訪問結束的總耗時):


640?wx_fmt=png


從上面的表格我們可以看到使用在併發量很小的場景,由於多執行緒可以完全使用裝置的計算核心,因此coobjc總耗時要比傳統多執行緒略高,但是由於整體耗時都很小,因此差異並不明顯,但是隨著併發量的增大,coobjc的優勢開始逐漸體現出來,當併發量超過1000以後,傳統多執行緒開始出現執行緒分配異常,而導致很多併發任務並沒有執行,因此在上表中顯示的是大於20秒,實際是任務已經無法正常執行了,但是coobjc仍然可以正常執行。


我們在手機淘寶這種超級App中嘗試了協程化改造,針對部分效能差的頁面,我們發現在滑動過程中存在很多主執行緒IO呼叫、資料解析,導致幀率下降嚴重,通過引入coobjc,在不改變原有業務程式碼的基礎上,通過全域性hook部分IO、資料解析方法,即可讓原來在主執行緒中同步執行的IO方法非同步執行,並且不影響原有的業務邏輯,通過測試驗證,這樣的改造在低端機(iPhone6及以下的機器)上的幀率有20%左右的提升。


優勢


簡明


  • 概念少:只有很少的幾個操作符,相比響應式幾十個操作符,簡直不能再簡單了。

  • 原理簡單:協程的實現原理很簡單,整個協程庫只有幾千行程式碼。


易用


  • 使用簡單:它的使用方式比GCD還要簡單,介面很少。

  • 改造方便:現有程式碼只需要進行很少的改動就可以協程化,同時我們針對系統庫提供了大量協程化介面。


清晰


  • 同步寫非同步邏輯:同步順序方式寫程式碼是人類最容易接受的方式,這可以極大的減少出錯的概率。

  • 可讀性高:使用協程方式編寫的程式碼比block巢狀寫出來的程式碼可讀性要高很多。


效能


  • 排程效能更快:協程本身不需要進行核心級執行緒的切換,排程效能快,即使建立上萬個協程也毫無壓力。

  • 減少卡頓卡死: 協程的使用以幫助開發減少鎖、訊號量的濫用,通過封裝會引起阻塞的IO等協程介面,可以從根源上減少卡頓、卡死,提升應用整體的效能。


總結


程式是寫來給人讀的,只會偶爾讓機器執行一下。——Abelson and Sussman


基於協程實現的程式設計正規化能夠幫助開發者編寫出更加優美、健壯、可讀性更強的程式碼。


協程可以幫助我們在編寫併發程式碼的過程中減少執行緒和鎖的使用,提升應用的效能和穩定性。


如果您有興趣加入手淘架構團隊,和我們一起投入到移動開發的新浪潮裡,歡迎投遞簡歷至 junzhan.yzw@taobao.com



640?wx_fmt=gif

你可能還喜歡

點選下方圖片即可閱讀



640?wx_fmt=jpeg

為拯救爸媽朋友圈,達摩院造了“謠言粉碎機”


640?wx_fmt=jpeg

如何成為優秀的技術主管?


640?wx_fmt=jpeg

這是工程師最長情的表白


640?wx_fmt=jpeg

關注「阿里技術」

把握前沿技術脈搏

相關文章