Javascript 的運行及瀏覽器多線程
2006.12 lu_yi_ming(_at_)sina.com
_ 本文目的
網頁HTML結構以及 Javascript 程式越來越複雜了,有必要整理一下思路。
本文只是想整理一下思路,很多細節不一定準確。
_ 問題詳述
現在一個網頁中包含了多個 Iframe(Frame),每個 Iframe 都有 window (本文中 window 特指 DOM 中的 window,MS Windows 中的視窗與 X Window 中的視窗用 WINDOW 來表示),每個 window 都可以有 setInterval,以及 setTimeout,另外 window.document 裡面還有很多的 element,每個 element 都可以有 event,而且這些 event 可以一直 bubble 到 body。
問題 1. 在一個 window 內部,event 與 window.setInterval / window.setTimeout 是否並行?
問題 2. window 之間的 Javascript 是否並行?
問題 3. 屬於同一個進程的 WINDOW 之間的 Javascript 是否並行?
問題 4. 如果有並行存在,是一種怎麼樣的並行? 是瀏覽器為每一個 window 或 WINDOW 類比了多線程,還是利用作業系統本身的多線程?
問題 5. 如果有並行存在,如何解決 Javascript 運行中的互斥、訊號?
_ 初步認識
經過一些簡單研究(因為懶,沒去讀 Firefox 原始碼),有以下初步認識。
對於 IE,用 SPY++ 分析,發現增加頁面中的 Iframe 並不能導致線程的增加,也不能導致子 WINDOW 的增加。所以,IE 中的 Iframe 及 window 是 Shell.Explorer (shdocvw.dll 中的 COM 組件) 內部實現的,可能在一個單獨的線程中,很可能就在 WINDOW 所屬的線程中。進一步,WINDOW 中所有 Iframe 的 window 的內部的 Javascript 都是不並行的。再進一步,結合網路傳輸,如果一個 Iframe.src 賦值後,Javascript 所在的線程應該會通知另一個線程(本文中簡稱通訊線程)去向伺服器發 HTTP 要求。通訊線程從伺服器端接收了一個完整的 HTTP 包後,將把這個資料包交給負責顯示的線程(中間環節可能有一個線程負責解析 html),顯示線程(或解析線程)邊顯示邊運行頁面中的 Javascript,例如 document.write()。並不停的繪製螢幕。而在這個 HTTP 資料包處理過程中,並不會處理其他 Iframe 的 window 的 setInterval 之類的事件。處理完一件事情後,才會在內部的 timer / timeout 隊列中選擇一個到期的開始運行(調用)。
所以,如果有一個 Iframe 的 window 的 Javascript 陷入死迴圈,則此線程內的所有的 Iframe 都將停止回應,並且從伺服器端接收來的新資料也將被擱置。
IE 中的多線程。在 IE 上按 Ctrl-N,或者運行 window.open() ,將會開啟一個新的 WINDOW,從 SPY++ 中看,會相應的增加一個新線程,管理這個新的 WINDOW。但是沒有增加更多的新線程,說明一個進程內所有的 WINDOW 都共用了通訊線程。也就是通訊進程管理了所有的 TCP Connection。 用 window.open() 返回的對象指標可以訪問這個新 WINDWO,並且可以訪問其下的 Iframe 的 window 中的各種資料,甚至也可以調用其中的函數,只是需要注意堆棧的不同。經過測試,如果兩個線程的 Javascript 同時訪問同一個資料,比如同時有 window.setInterval 指定的函數去訪問一個 window 中的一個 element 的變數,或者修改屬性,則其中的一個函數會返回 Javascript Exception:拒絕訪問,說明瀏覽器在內部實現中注意了互斥。
基於以上認識,對於 IE ,在一個 WINDOW 的所有 Iframe 的 window 內部應該不存在多線程,也不存在 Javascript 並行。當然也不會有 sleep 之類的函數了。把一個 Javascript 函數阻塞了(如特定的 ActiveX )可能導致整個 WINDOW 暫停相應。
關於 IE 還有一個發現,window.setInterval 不是用 WM_TIMER 實現的。
關於 Firefox,在 Windows 上,用 SPY++ 分析,頁面中的每一個 Iframe 都是用一個 WINDOW 實現的。但是所有的 WINDOW,包括其他 Tab 開啟的 WINDOW,都屬於一個線程。但是跨 Tab ,跨瀏覽器視窗訪問同時訪問一個資料,也會引發 Javascript Exception:拒絕訪問,說明 Firefox 的 Javascript 很可能是在一個 Tab 範圍內,是不並行的。有可能是 Firefox 給每個 Tab 單獨開了一個線程來運行 Javascript。反正最終行為要與 IE 保持差不多,估計也就這樣了。
在 Linux(FC6)上啟動 Firefox,用 gdb attach 進去,發現在新開啟網頁時會增加不少線程,但網頁顯示穩定後過一段時間,線程數會減少。這也可能是因為運行 Javascript 的線程任務做完了。
_ 初步結論
1. 在 IE 的一個 WINDOW 內,Firefox 的一個 Tab 內, Javascript 是單線程啟動並執行。
2. 如果要在頁面上的各 Iframe 之間實現 Javascript 同步,則應該採用 window.setInterval 來實現,而不是把一個 Javascript 掛起。
3. 在 IE 上跨 WINDOW,或在 Firefox 上跨 Tab 訪問資料,則要注意用 try catch 解決線程互斥導致的 Exception。