平行處理是一個非常重要的程式設計方法,你幾乎不會不遇到它,在未來越來越多複雜的程式應用中,平行處理一定跑不掉。
「平行處理,我會啊,Java裡面不就不包含有 thread,用它就沒錯了」
沒錯,Java 一開始出來時,在語言層級直接支援 thread,以那時的時空背景,確實是令人眼睛一亮的特色。
「Java有 thread、有 synchronized (lock),還有一大堆豐富的資料結構供我們使用,還不夠嗎?」
以上確實都是使用 Java 撰寫平行處理程式時很重要工具。
可是,你若曾經使用 Java 的 thread 來開發過程式,你會發現並不是那麼好寫。你會遇到不少狀況,不是程式不小心就給你停住(deadlock啦),要不就是資料有時對有時不對(發生 race condition 了),要讓資料穩穩的走,實在不容易。
可是,你若曾經使用 Java 的 thread 來開發過程式,你會發現並不是那麼好寫。你會遇到不少狀況,不是程式不小心就給你停住(deadlock啦),要不就是資料有時對有時不對(發生 race condition 了),要讓資料穩穩的走,實在不容易。
以上的這些感覺非常正常,不單是你,這也是大家都困繞擾的問題,因此才會持續有平行處理的解決方案出現。
Java 直到現在仍無法擺脫平行處理的夢靨,不單單是 Java,平行處理幾乎是所有語言面對的共同難題。雖然到了 Java 7,Java 對平行處理的解決方案,仍然顯得不夠完整。
Concurrency 這麼難纏主要的問題出在於共享的資料上,各個 thread 在不同的時間去存取同一個資料,造成資料的不一致。
為要解決這個不一致的問題,lock、critical section 等各種解決方案老早就出現。
為要解決這個不一致的問題,lock、critical section 等各種解決方案老早就出現。
但有了這些解決方案,我們還是要很小心,執行以下的步驟
1. 在多個 thread 會共享到的 class中,一個個加上 synchronized 來保護我們的共享資料。
2. 避免加上太多的 synchronized,以免失去了concurrency的好處。
1. 在多個 thread 會共享到的 class中,一個個加上 synchronized 來保護我們的共享資料。
2. 避免加上太多的 synchronized,以免失去了concurrency的好處。
這在簡單的應用中不會有問題,可是你是否發現,當你的資料一多,或是資料結構間互相使用的關係變多時,問題就來了。synchronized 不好好檢查與運用,deadlock絕對有你的份。
你若上網查資料,你可能會找到許多在 Java 中開發 concurrency 的好方法,其中可能包括以下的 guideline
1.盡量使用 final 的資料
2.lock 的順序需要保持一致(避免 deadlock)
3.盡量使用 Local Thread 變數
4.盡量使用 stack 中的資料,也就是參數或 method 中的 local variable
有這麼多的建議,代表這個問題的困難程度。以上這些建議都是好的,都有機會解決你一些困擾,可是為甚麼還是困難重重?
有這麼多的建議,代表這個問題的困難程度。以上這些建議都是好的,都有機會解決你一些困擾,可是為甚麼還是困難重重?
Doug Lea在 Java 界是一個 concurrency 的大專家,java.util.concurrent 是他的傑作,你沒看錯,Java 附的 concurrent package是他做的。他的 Fork/Join framework 是 Java 7 解決 concurrency 的重要解決方案,你若繼續使用 Java 開發程式絕對不能錯過。
由 Doug Lea 仍持續加強它的 concurrency framework,你就知道 concurrency 多麼難以處理。
在各種語言中解決 concurrency,最被人稱許的,大概數 Erlang了。
Erlang 一開始就號稱使用在電信產業,那種需要特別穩定與超高數量級的資料處理中,Erlang 對於 concurrency 確實有他的一套。
Erlang 一開始就號稱使用在電信產業,那種需要特別穩定與超高數量級的資料處理中,Erlang 對於 concurrency 確實有他的一套。
Erlang 將 concurrency解決得這麼穩定,在於以下的手法
1.No shared state:不要有共享的資料。
2.Lightweight processes:使用 process 為主要的處理對象,這裡的 process 不是 OS 中的 process,請不要誤會。
3.Asynchronous message-passing:使用訊息傳遞作為不同 process 間的溝通機制
4.Mailboxes to buffer incoming messages:使用 mailbox 儲存 message
5.Mailbox processing with pattern matching:使用 pattern matching 來比對
上述這些手段就是所謂的 Actor Model,當然這種觀念不是 Erlang 所獨有。
除以上幾段外,Erlang 可以完整解決平行處理的問題,其手段還包含
1.儘量使用函數
2.儘量使用常數
3.沒有 lock
4.沒有 side-effect
這些觀念都非常的重要,都是寫好 concurrency 程式的重要概念,請努力記住。在你學習 Scala 的過程中,這些都是一再要提醒自己的事。
Concurrency 絕非天上掉下來的禮物就自然解決的,許多觀念與思考邏輯的改變,才是解決 concurrency 的不二法門。
網路上有許多文章號稱 Scala 的 actor model 完美解決 concurrency 問題,你若因為這些文章來學習 Scala,你會大失所望。 Actor model 不是解決 concurrency 的萬靈丹,你的觀念才是!
單純的以為 actor model 的 message passing 就可解決 concurrency 這個大問題,Doug Lea 還需要那麼辛苦幹嘛?!
再回頭看清楚 Erlang 處理 concurrency 的原則,actor model 是其一,更重要的在後面,函數、常數、沒有 lock、沒有 side-effect,請看清楚,多看幾遍,咀嚼這些的含意,為何 Erlang 還需強調這些東西。
Data sharing 是 concurrency 的問題來源,直接解決這個問題來源才是做好 concurrency 的根本。但我們的程式通常是攪和來、攪和去,運行在單一 thread 上當然可以,多個 thread,嗯嗯...。請注意,等候 method 的 return 也是一種 data sharing。
Concurrency 絕非天上掉下來的禮物就自然解決的,許多觀念與思考邏輯的改變,才是解決 concurrency 的不二法門。
網路上有許多文章號稱 Scala 的 actor model 完美解決 concurrency 問題,你若因為這些文章來學習 Scala,你會大失所望。 Actor model 不是解決 concurrency 的萬靈丹,你的觀念才是!
單純的以為 actor model 的 message passing 就可解決 concurrency 這個大問題,Doug Lea 還需要那麼辛苦幹嘛?!
再回頭看清楚 Erlang 處理 concurrency 的原則,actor model 是其一,更重要的在後面,函數、常數、沒有 lock、沒有 side-effect,請看清楚,多看幾遍,咀嚼這些的含意,為何 Erlang 還需強調這些東西。
Data sharing 是 concurrency 的問題來源,直接解決這個問題來源才是做好 concurrency 的根本。但我們的程式通常是攪和來、攪和去,運行在單一 thread 上當然可以,多個 thread,嗯嗯...。請注意,等候 method 的 return 也是一種 data sharing。
這裡順帶一提,因為 lock 很容易造成 deadlock,所以使用 synchronized 來 lock 資料的機制,在 Scala 語言已經被移除,Scala 以沒有 synchronized 這種語法,當然你還是可以使用其他的 method 加上。
怪吧!這麼好用的 lock 機制,竟然被移除,違反你以前的學習觀念吧?!
怪吧!這麼好用的 lock 機制,竟然被移除,違反你以前的學習觀念吧?!
為何要移除 synchronized 呢?當然是有其原因。Scala知道共享式資料才是造成 concurrency 問題的最大元兇,所以降低資料共享才是真正解決 concurrency 的最正確作法。
在 Scala 中會一再要求你,盡量符合以下的規範
在 Scala 中會一再要求你,盡量符合以下的規範
1.盡量使用不可變的資料:變數使用 immutable 與 val
2.盡量使用 function:最好是跟純函數式的語言一樣,使用沒有 side-effect 的函數
3.盡量不要資料共享
4.使用 actor model,並使用 asynchronous message-passing:在 actor 中使用非同步的訊息傳遞
5.不要 lock:既然上面都已做到,已經不需要使用 lock 來保護資料了(此時使用 lock 反而容易造成程式 deadlock 的可能性)
第4點的非同步傳遞訊息,就是不等待對方回應的訊息傳遞,訊息傳遞完畢後就回來做自己的事,不需等待對方回應。
「為何不等待對方回應這麼重要呢?」
因為呼叫方 (caller) 若等待被呼叫方 (callee) 回應,callee 為要達成 caller 所要求的事項,可能需要進行一大堆的工作,而這些工作可能包含 callee 回頭呼叫 caller 的工作,若 callee 回頭呼叫的方式也是需要等待Caller完成才會繼續執行,這時問題發生:
1. caller 呼叫 callee 並等待
2. callee 呼叫 aller 並等待
以上兩種情況一發生,那就叫 deadlock,無解了,程式自然停下來,乾瞪眼!
所以非同步的訊息傳送才會這麼重要。但實際狀況卻是「同步的訊息傳送」有時避免不了,這時,你需要謹慎使用「同步的訊息傳送」。
平行處理是一個大問題,可是卻也是一個非解不可的問題,Scala 準備好各種好用的工具與寫程式的準繩,就看你會不會好好利用。
若你順著 Scala 幫你準備的東西,其實平行處理並不像想像那麼難,而且會這些工具還會讓你程式開發的速度與品質大幅增加。
沒有留言:
張貼留言