發表文章

目前顯示的是 二月, 2013的文章

GUI 架構的考量

( 和 Scott 聊過後, 決定改寫《非同步程式新手心得: 為什麼要用單一 thread + task queue 的方式設計架構》的前半段。比較完整地描述 GUI 架構的入門心得。 ) GUI 需要低延遲的反應速度, 隨時能接收使用者輸入和並提供畫面回饋。這表示要在不互相阻檔的前提下應付三種工作: 更新畫面 偵測使用者輸入 處理使用者輸入 甚至第四種情況: 網路輸入 若是使用 multi-thread 處理上述情況 (比方說有三個 thread 分別專職處理前述三件工作), 必須留意 thread 之間有共享資料的情況。為了避免 race condition 必須使用 lock; 使用 lock 後又要擔心 dead lock。 使用 global lock 有可能成效不彰, 比方說更新畫面太久, 而沒有即時回饋使用者的輸入。另一方面, 切許多細小的 lock 處理各塊共享資料, 容易出錯造成 dead lock, 或誤以為有處理好卻仍存在 race condition。 反過來說, 若每個工作 (#1) 都切得很細, 執行時間不長, 但可能需要多個工作單元才能完成目標。這樣就能在一個 thread 內以類似 queue consumer 的方式執行全部的工作。有新的需求時, 放入工作到 queue 裡, 大致上可確保相依工作之間的執行順序 (#2)。由於全部工作都在同一 thread 執行, 自然不用擔心 dead lock 或 race condition。 於是可以看到, 像 Gtk、iOS、Android 的平台, 都將更新畫面、偵測事件和處理事件三者整合在 main thread 處理, 並且假設程式以 single thread 的方式執行 (#3)。 我不清楚其它平台的情況, iOS 有提供 NSURLRequest 應付常見的網路需求。讀寫資料和事件的 callback 也都在 main thread 內執行。 這麼一來, 框架開發者省事許多, 使用框架的開發者也可以安心地實作 callback。 備註#1 可想像每個工作是一個函式, 在 iOS 或 Android 上, 整合這種「工作 queue」的概念到基本框架裡, 用起來很直覺, 希望盡量避免開發者從 thread 的角度去處理事情。 #2 不行的時候只好加入狀態變數了解目前…

async 與 non-blocking IO

以前一直沒有分清楚 asynchronous 和 non-blocking 的差異, 寫久了以後才明白兩者是不同層次的東西。 asynchronous programming: 程式不是一條線走到底, 可能目前做到一個段落停下來, 「過一會兒」再繼續執行 non-blocking IO: 呼叫讀寫函式時, 不會等到完成才返回 通常要讀寫外部資料時 (一般檔案、網路、pipe 等) , 無法確定返回的時間, 這樣會阻擋程式的運作, 所以希望這個讀取 (或寫入) 不要等完成後才返回 (即「非同步」)。 若不希望此次呼叫有不確定時間的擔擱, 有兩種可能的作法: 開新 thread 或用既有的 thread 進行讀寫。總之, 停是停別人家的 thread, 和本 thread 無關 設定 file descriptor 為 O_NONBLOCK, 告訴 OS 這個 fd 為 non-blocking IO, 呼叫後要能立即返回 兩者作法的優缺點分析已超出本「隨手記」的範圍 (書本作者愛用的大絕), 需要特別注意的是, 設了 O_NONBLOCK 不表示呼叫 read/write 後就結束了。 man 2 read (或 man 2 write) 的 ERRORS 提到:EAGAIN The file descriptor fd refers to a file other than a socket and has been marked nonblocking (O_NONBLOCK), and the read would block.EAGAIN or EWOULDBLOCK The file descriptor fd refers to a socket and has been marked nonblocking (O_NONBLOCK), and the read would block. POSIX.1-2001 allows either error to be returned for this case, and does not require these constants to have the same value, so a portable application should check for both possibil…

vim 顯示目前函式的名稱

我平常習慣用 gj 跳到某個 symbol 位置, 但接下來上下讀程式時, 有時需要知道目前在那個函式裡, 幸好有前人提供 script 做這件事。省了自己摸索的時間。 藉這機會順便學一些 vim script 的語法: fun! ShowFuncName() let lnum = line(".") " (1) let col = col(".") echohl ModeMsg " (2) echo getline(search("^[^ \t#/]\\{2}.*[^:]\s*$", 'bW')) " (3) echohl None call search("\\%" . lnum . "l" . "\\%" . col . "c") "(4) endfun map F :call ShowFuncName() <CR> " (5) 注意 vim 是用 「"」 表示後面的字為註解 1. let VAR = VALUE: vim 的設值 line("."): expression, 取回目前行數 col("."): expression, 取回目前欄數 這裡存下目前的位置, 在 (4) 的時候可以跳回原位 (後述)。 2. 切換 echo 字串 highlight 的方式, 比方 echohl WarningMsg 預設會顯示紅字 3. :call FUNCTION(...): 呼叫函式 FUNCTION getline(N): 顯示檔案內第 N 行字串 search(REGEXP, FLAG): 搜尋字串, 跳到符合的位置並傳回顯示符合的行數 :call search(REGEXP, 'bW'): b 表示往上搜; W 表示搜到底的時候, 不用從頭 (或尾) 繼續搜。見 Searching - Vim Tips Wiki 了解更多 search 技巧 所以這一行的意思是往上找到開頭「兩個字不為空白字元和註解開頭」的行, 並且此行結…

非同步程式新手心得: 為什麼要用單一 thread + task queue 的方式設計架構

最近多了一些 GUI + 非同步IO 的開發經驗, 簡記一下心得。 GUI 架構背後的考量 由於 GUI 需要低延遲的反應速度, 必須使用非同步的方式實作。 Gtk/Android/iOS 都在 main thread 裡處理畫面顯示和事件 callback 是有道理的。藉由架構保證所有 task (繪圖、偵測事件、事件 callback) 在同一 thread 內執行, 寫程式時不用擔心 race condition。反之, 若將繪圖、偵測事件、事件 callback 三者拆到兩個 threads 以上, 會衍生許多潛在問題。 共享變數太雜時, 使用 global lock + multi-thread 無法提高太多效能, 失去 multi-thread 的意義。 若依各別變數使用不同的 lock 容易寫出問題。 在同一個 thread 內執行內全部的 task, 可以避免使用 lock, 相對省事許多。然後用戶有效能需求時, 針對有需求的部份另開 thread 加速, 減少需要使用 lock 的地方。實作注意事項 由於程式不是一路通到底, 不適合用以往的直線方式思考。 盡量從環境狀態的角度了解如何處理當下的 task, 而減少保證 task 之間的執行順序。這樣寫起來比較簡單, 也會比較穩定。 程式本身或註解要能表示清楚狀態變化對各 task 的影響。提供 debug flag 可以 log 各 task 執行順序, 或註解提及主要的和特別的 task flow, 應該有助於日後維護。 若能留下產生每個 task 的 task 為何, 有助於日後在 debugger 內除錯。 開始實作以前, 要想清楚可能的狀態變化, 以及留好協助維護的 debug code, 由 debug flag 可以開關。

iOS crash 除錯小技巧

參考文章:My App Crashed, Now What? – Part 1My App Crashed, Now What? – Part 2 摘錄重點: SIGABRT 比較好解, 是 framework 偵測到有異常, 能更接近問題的源頭; EXC_BAD_ACCESS 發生在存取記憶體出錯, 離案發現場可能有一段時間 記得在 breakpoint 的輔助畫面君上 "Exception All + Break on Throw" 的 breakpoint, 可在有 exception 時看到 backtrace edit scheme -> Diagnostics -> Enable Zombie Objects: 有機會在存取到已 release 的物件時, 當下抓到錯誤, 避免到後面出現不知所已的 EXC_BAD_ACCESS 之前用了 "Break on Throw" 和 "Enable Zombie Objects" 後, 減少了不少除錯時間。

Objective-C++ 使用 C function 的注意事項

寫 Objective-C++ (*.mm) 的時候, 記得規則和 C++ 一樣, 函式名稱會有name mangling。呼叫純 C 的函式時, 要留意編譯 C 的原始碼時, 是用 C 或 C++ 的方式編譯。若 object file 是用 C 的方式編譯, 引入的 header 檔要加上 "extern C" 的語法。 話說我是用 nm 看編出的 static lib 和呼叫它的 object file 時, 發現有兩個長得很像但不一樣的函式名稱, 才想起這件事。 相關文章:從 C 呼叫 C++ 函式的過程理解程式編譯、連結的原理Linker error calling C-Function from Objective-C++ - Stack Overflow

XCode 編譯連結 static library 的注意事項

最近遇到兩個狀況 link 執行檔時, 找不到 static library 內用的 symbol link 執行檔時, 找不到 Core Data 用的 schema (*.momd) 重新回憶《解決 undefined symbol / reference》, 想到 Mac OS/iOS 也是和 Linux 類似的架構, 原理應該一樣。上網搜了相關文章, 得到以下的解法: static library 只能含有 object file, target 為 static library 的時候, 設定 Build Phases 裡的 Link Binary With Libraries 沒有意義。應該要設 application target 的 Link Binary With Libraries, 因為是建立 application (unit test, etc) 時才會 link libraries 同上, static library 不能含有其它 resource file, 要放在其它 target 的 bundle 裡, 然後 static library 裡的程式讀另外的 bundle。 參考資料:Smarter and More Reusable Core Data