BeOS操作系統最鮮明的特色在於“普適多線程(pervasive multithreading)”技術。 以現在的標準來衡量,BeBox和其他運行BeOS操作系統的計算機充分利用了計算資源。 BeBox的演示令人印象深刻。 66MHz雙處理器計算機能夠流暢地運行多個視頻並在後台播放CD中的很多音軌——與此同時,用戶界面響應也保持一貫的流暢。 BeOS操作系統讓很多技術狂熱者大跌眼鏡,他們當中的許多人堅持認為,即便是目前的許多台式機,操作體驗仍舊無法與當年的BeOS相媲美 。
19年以來,無數工程技術人員嘔心瀝血把自己關在實驗室工作室裡試圖逾越“並行處理”這一難題;19年後的今天,我們有了武裝了GCD技術的Snow Leopard系統。 誠然,我們可以看到GCD通過一些手段避免了當年的BeOS系統所遭遇的問題(諸如線程的重複使用,對全局線程池(global pool of threads)的維護和管理,等等)。 儘管Apple的牛b吹得很大,我們不禁還是會產生這樣一個疑問,這一切真的比BeOS所採用的多線程技術更為優秀麼?
GCD技術中滲透了一種與BeOS的“普適多線程(pervasive multithreading)”設計理念截然相反的概念。 為了獲得良好的全局響應,BeOS不惜讓應用程序的每一個組件(甚至包括每一個窗口)都在其自己的線程中運行,這無疑為程序開發者增加了沉重的負擔。 而GCD則採用了一種更具限制性的、分級的方式:專門分配一個主程序線程用來處理所有用戶事件,而(其他)工作線程用來在需要的時候執行特定的任務。 換句話說,GCD不需要程序開發者來思考究竟應該如何為程序分配多個並行線程(當然GCD也會在必要的時候協助開發者進行線程的分配與優化工作)。
Mac OS X系統中,當程序對系統事件無響應時,用戶會看到一個這樣的轉動的“彩球”:
。 誠然,這是不少Mac用戶非常痛恨的東西——無論你的機器是四核還是八核還是四百核八百核。 很多開發者也深諳此理,但是為什麼我們仍舊會時常看到這個東西呢? 為什麼不干脆讓所有程序在背景線程中執行那些冗長的任務呢?
原因是多方面的。 此前我們也談到過一些,例如我們很難確切知道究竟應該創建多少個線程才合適,而最重要的一個原因卻在於,創建一個線程並不是一件輕鬆的事情。 並不是說技術上有多困難,而是說,從單純地操控程序中的現行工作,到涉足系統層面的任務管理,其間有太多問題需要站在更為全局的角度進行思考與權衡。
然而不幸的是,事實上有相當多非常普通的任務,通常情況下程序只需很短的時間即可完成,但是如果其間出現了一些差錯,就會耗費相當長的時間。 一切涉及文件系統的工作都有可能引發操作系統底層的混亂。 對於名稱檢索(name lookups)也是類似的情況,通常會瞬間完成,但是當系統不緊不慢地返回結果的時候就可能使許多其他程序出現各種各樣的問題。 所以,即便是相當謹慎的Mac OS X程序也可能最終扔給我們一個惱人的“彩球”。
對於GCD,Apple告訴我們,情況不會是這個樣子。 來看一個例子,假設一個程序中有一個按鈕,點擊時會分析當前的文檔並返回一些統計結果。 通常情況下這種分析會在不到一秒鐘內完成,該按鈕的執行代碼如下:
函數主體中的第一行用來“分析”這個文檔,第二行用來更新程序的內部狀態,第三行向程序反饋該統計結果需要被更新以反映這個新狀態。 一切都是很平常的樣式,而且,只要這些步驟(都在主線程上運行)不花費太長的時間,一切都將正常。 在用戶點擊按鈕之後,應用程序主線程會及時處理用戶輸入動作並回到主事件循環以執行接下來的用戶動作。
但是如果用戶打開了一個非常大非常複雜的文檔,情況就不一定是如此了。 突然間,“分析”步驟無法在1~2秒內完成,而是需要花費15~30秒——於是我們又看到了熟悉的“彩球”。 當然,程序員可能會jj歪歪,“這只是很極端的情況,通常用戶不會打開這麼大這麼複雜的文檔。而且我實在懶得翻手冊然後給這段很簡單的函數添加額外的代碼了… ”
可是,如果我告訴您,您可以只添加2行代碼把“分析”步驟挪到後台,而所有這些都在現有函數內部,您覺得如何? 沒有程序全局對象,沒有線程管理,沒有關聯對象,甚至沒有額外的變量…請看:
這簡單的兩行代碼,蘊含了許多門道。 所有GCD中的功能都始於dispatch_ ,就是上面代碼中藍色的部分,這段代碼的關鍵在於第二個dispatch_async() 。 之前我們提到了“工作單元(units of work)”,但是並沒有詳細說明GCD究竟如何操控的。 可是現在看起來答案似乎應該很明顯了:block(請參閱《 持續完善,構建編程友好型環境(連載10/23) 》)。 block允許GCD接口直接嵌入現有的代碼中,而不要求任何額外的設定或重構等。
然而,這段代碼中最引人注目之處在於,當後台任務完成並反饋結果時,它是如何檢測到的。 在同步代碼中(synchronous code),“分析”步驟調用的代碼以及用來更新程序顯示的代碼僅按照既定的次序在函數內部出現。 而在異步代碼中(asynchronous code),情況仍然是如此。
上述代碼中,外層的dispatch_async()在全局GCD對列中放置了一個任務,該任務是由作為第二個參數傳遞的block代表的,考慮到了“分析”步驟可能耗費的潛在時間,並囊括了其他放置在該主隊列上的任務(運行在主線程上的隊列)。 內層block中的代碼是無法在其他任何地方運行的。 設想一下,如果在“分析”步驟完成之後讓後台線程向主線程發送某種“通知”,還需要添加一些代碼來對此加以識別並處理,顯然這是相當繁瑣的。 當“分析”步驟完成之後,內層block被放置於主隊列當中並在主線程上運行,最終更新用戶界面。
顯然,GCD提供了一種簡潔而高效的方式。 對於開發或者來說,無疑這是最佳的解決方案。
Snow Leopard中添加的所有API中,GCD對於未來的Mac OS X具有最為深遠的指向意義。 異步執行以及在許多CPU之間分派工作負載(workload),從未如此簡單過。
此前,第一次聽說Grand Central Dispatch時,我對此持懷疑態度。 在計算機科學領域中,如何簡潔而高效地解決並行計算問題,這一難題已經持續了幾十年之久。 而現在Apple聲稱能夠解決這一問題? 聽起來有點荒謬哈…
事實上Grand Central Dispatch並不染指這一問題,不會協助將您的工作分割為多個獨立的可執行任務——或是決定哪些可以或應當異步/並行執行。 GCD所作的則更為務實。 一旦開發者意識到某項任務可以被分割並提取出來,GCD能夠使其盡可能地簡單、高效地執行。
然而,FIFO序列的使用,尤其是序列化隊列(serialized queue)的存在,看起來似乎有悖於無處不在的“並行”思想。但是我們已經能夠清晰地看到多線程趨勢所指引的方向,而這對廣大開發者來說無疑是一個不小的挑戰。
對於Grand Central Dispatch,Apple的一個口號是:“並行”海洋中的“串行”島嶼(islands of serialization in a sea of concurrency)。 而正是這些“串行島嶼”將開發者從同步數據訪問(simulaneous data access)、死鎖(deadlock)等多線程處理時經常遇到的棘手問題中解脫出來(譯註: 詳情請參考developer. apple.com )。 GCD倡導程序員從程序中識別出那些可以在主線程上更好執行的函數,並能夠輕鬆地在保持子任務之間現有的次序及獨立性的基礎上將整個工作單元分離出來。
是的,隊列與線程的執行可以是優雅而簡潔的,將其整合入底層操作系統則能夠在直觀上降低使用的門檻,然而真正使得GCD廣受開發者歡迎的,卻是構建於block的API。 正如我們評價說Time Machine是“第一種人們真正樂於使用的備份系統”,GCD有望最終將曾經讓無數人頭疼不已的異步程序設計推廣到所有Mac OS X開發者當中。
19年以來,無數工程技術人員嘔心瀝血把自己關在實驗室工作室裡試圖逾越“並行處理”這一難題;19年後的今天,我們有了武裝了GCD技術的Snow Leopard系統。 誠然,我們可以看到GCD通過一些手段避免了當年的BeOS系統所遭遇的問題(諸如線程的重複使用,對全局線程池(global pool of threads)的維護和管理,等等)。 儘管Apple的牛b吹得很大,我們不禁還是會產生這樣一個疑問,這一切真的比BeOS所採用的多線程技術更為優秀麼?
GCD技術中滲透了一種與BeOS的“普適多線程(pervasive multithreading)”設計理念截然相反的概念。 為了獲得良好的全局響應,BeOS不惜讓應用程序的每一個組件(甚至包括每一個窗口)都在其自己的線程中運行,這無疑為程序開發者增加了沉重的負擔。 而GCD則採用了一種更具限制性的、分級的方式:專門分配一個主程序線程用來處理所有用戶事件,而(其他)工作線程用來在需要的時候執行特定的任務。 換句話說,GCD不需要程序開發者來思考究竟應該如何為程序分配多個並行線程(當然GCD也會在必要的時候協助開發者進行線程的分配與優化工作)。
Mac OS X系統中,當程序對系統事件無響應時,用戶會看到一個這樣的轉動的“彩球”:
原因是多方面的。 此前我們也談到過一些,例如我們很難確切知道究竟應該創建多少個線程才合適,而最重要的一個原因卻在於,創建一個線程並不是一件輕鬆的事情。 並不是說技術上有多困難,而是說,從單純地操控程序中的現行工作,到涉足系統層面的任務管理,其間有太多問題需要站在更為全局的角度進行思考與權衡。
然而不幸的是,事實上有相當多非常普通的任務,通常情況下程序只需很短的時間即可完成,但是如果其間出現了一些差錯,就會耗費相當長的時間。 一切涉及文件系統的工作都有可能引發操作系統底層的混亂。 對於名稱檢索(name lookups)也是類似的情況,通常會瞬間完成,但是當系統不緊不慢地返回結果的時候就可能使許多其他程序出現各種各樣的問題。 所以,即便是相當謹慎的Mac OS X程序也可能最終扔給我們一個惱人的“彩球”。
對於GCD,Apple告訴我們,情況不會是這個樣子。 來看一個例子,假設一個程序中有一個按鈕,點擊時會分析當前的文檔並返回一些統計結果。 通常情況下這種分析會在不到一秒鐘內完成,該按鈕的執行代碼如下:
但是如果用戶打開了一個非常大非常複雜的文檔,情況就不一定是如此了。 突然間,“分析”步驟無法在1~2秒內完成,而是需要花費15~30秒——於是我們又看到了熟悉的“彩球”。 當然,程序員可能會jj歪歪,“這只是很極端的情況,通常用戶不會打開這麼大這麼複雜的文檔。而且我實在懶得翻手冊然後給這段很簡單的函數添加額外的代碼了… ”
可是,如果我告訴您,您可以只添加2行代碼把“分析”步驟挪到後台,而所有這些都在現有函數內部,您覺得如何? 沒有程序全局對象,沒有線程管理,沒有關聯對象,甚至沒有額外的變量…請看:
然而,這段代碼中最引人注目之處在於,當後台任務完成並反饋結果時,它是如何檢測到的。 在同步代碼中(synchronous code),“分析”步驟調用的代碼以及用來更新程序顯示的代碼僅按照既定的次序在函數內部出現。 而在異步代碼中(asynchronous code),情況仍然是如此。
上述代碼中,外層的dispatch_async()在全局GCD對列中放置了一個任務,該任務是由作為第二個參數傳遞的block代表的,考慮到了“分析”步驟可能耗費的潛在時間,並囊括了其他放置在該主隊列上的任務(運行在主線程上的隊列)。 內層block中的代碼是無法在其他任何地方運行的。 設想一下,如果在“分析”步驟完成之後讓後台線程向主線程發送某種“通知”,還需要添加一些代碼來對此加以識別並處理,顯然這是相當繁瑣的。 當“分析”步驟完成之後,內層block被放置於主隊列當中並在主線程上運行,最終更新用戶界面。
顯然,GCD提供了一種簡潔而高效的方式。 對於開發或者來說,無疑這是最佳的解決方案。
Snow Leopard中添加的所有API中,GCD對於未來的Mac OS X具有最為深遠的指向意義。 異步執行以及在許多CPU之間分派工作負載(workload),從未如此簡單過。
此前,第一次聽說Grand Central Dispatch時,我對此持懷疑態度。 在計算機科學領域中,如何簡潔而高效地解決並行計算問題,這一難題已經持續了幾十年之久。 而現在Apple聲稱能夠解決這一問題? 聽起來有點荒謬哈…
事實上Grand Central Dispatch並不染指這一問題,不會協助將您的工作分割為多個獨立的可執行任務——或是決定哪些可以或應當異步/並行執行。 GCD所作的則更為務實。 一旦開發者意識到某項任務可以被分割並提取出來,GCD能夠使其盡可能地簡單、高效地執行。
然而,FIFO序列的使用,尤其是序列化隊列(serialized queue)的存在,看起來似乎有悖於無處不在的“並行”思想。但是我們已經能夠清晰地看到多線程趨勢所指引的方向,而這對廣大開發者來說無疑是一個不小的挑戰。
對於Grand Central Dispatch,Apple的一個口號是:“並行”海洋中的“串行”島嶼(islands of serialization in a sea of concurrency)。 而正是這些“串行島嶼”將開發者從同步數據訪問(simulaneous data access)、死鎖(deadlock)等多線程處理時經常遇到的棘手問題中解脫出來(譯註: 詳情請參考developer. apple.com )。 GCD倡導程序員從程序中識別出那些可以在主線程上更好執行的函數,並能夠輕鬆地在保持子任務之間現有的次序及獨立性的基礎上將整個工作單元分離出來。
是的,隊列與線程的執行可以是優雅而簡潔的,將其整合入底層操作系統則能夠在直觀上降低使用的門檻,然而真正使得GCD廣受開發者歡迎的,卻是構建於block的API。 正如我們評價說Time Machine是“第一種人們真正樂於使用的備份系統”,GCD有望最終將曾經讓無數人頭疼不已的異步程序設計推廣到所有Mac OS X開發者當中。