這天,來了新的角色,觀月歌帆,她是小櫻他們班的新老師,但這位老師的法力高強,一眼就被小狼視破,劇情究竟會怎麼發展呢?讓我們繼續看下去~
集數來到第 26 集,觀月歌帆可說是第一季的轉折點,就好比 Go routing 一樣,雖然 Golang 的錯誤處理也是經典,但 Go routing 更是經典中的經典,地表上幾乎所有的魔法都沒有 Golang 的 Go routing 強,執行迅快、輕量、簡單好上手,沒有其他魔法能同時擁有這三個特性。
執行緒是電腦進行排程時最小的單位。在現在的CPU中,通常可以同時處理好幾個執行緒,因為可以同時做好幾件事,可以提升整台電腦的效能。
就好比小時候被老師要求罰寫,一個人寫可能要一小時,但如果外包出去再給另一位同學寫就只需要半小時。在電腦中也是類似這樣的運作。當然並不會這麼剛好兩個人罰抄就是半小時,因為在分配工作前要先進行溝通,所以並不會說兩個人罰抄就一定能足足快兩倍。不過,如果在事前工作分配的效率就很高,那麼效率一定就越好,而能花最少時間就找到同學代抄並安排好範圍的角色非 Golang 莫屬。
我們一般寫的程式主要都是在主執行緒上,如果要啟用另一條執行緒,只要在主執行緒中呼叫函式前加上 go
即可
package main
import "fmt"
func foo(){
for i := 0; i<5; i = i + 1{
fmt.Println("副執行緒")
}
}
func main(){
go foo()
for i := 0; i<5; i = i + 1{
fmt.Println("主執行緒")
}
}
執行結果:
主執行緒
主執行緒
主執行緒
主執行緒
主執行緒
休旦幾累,副執行緒怎麼沒有被執行??
因為在啟動另一條執行緒時會需要一段時間(雖然很快),但是在還沒啟動完畢時,主執行序就已經結束了,主執行緒一旦結束命令提示字元就會停止。
那麼要怎麼辦顯示「副執行緒」呢?其實只要簡單的用一個等待函式讓主執行序稍微等待一下就可了
package main
import(
"fmt"
"time"
)
func foo(){
for i := 0; i < 5; i = i + 1{
fmt.Println("副執行緒")
}
}
func main(){
go foo()
for i := 0; i < 5; i = i + 1{
fmt.Println("主執行緒")
}
// 讓主執行序暫停三秒,稍微等待 foo() 結束
time.Sleep(3 * time.Second)
}
執行結果:
副執行緒
副執行緒
副執行緒
副執行緒
副執行緒
主執行緒
主執行緒
主執行緒
主執行緒
主執行緒
執行結果每次都不一樣,由作業系統決定
為什麼一定要等 3 秒?其實等 1 秒也可以吧?有沒有一個更明確的方法?其實是有的,我們可以透過副執行序在完成工作後回傳一個變數,並讓主執行序等待這個變數,直到收到才結束。而這個變數因為要在執行序和執行序之間傳遞所以是個特別的型態!
我們可以想像一下,在執行緒之間有一個共用的通道,執行緒可以利用這個通道來交換資訊。
利用 channel 傳遞資訊通常分為三個步驟
channel 上的變數跟一般變數使用沒什麼不同,只是多了 chan 的前綴,比如原先的 int
在 channel 上叫作 chan int
,宣告的方法很簡單只要使用 make() 並以型態為參數即可 make(chan int)
, make(chan bool)
,...
package main
import "fmt"
func main(){
myChannel := make(chan bool)
// 印出 myChannel 的型態
fmt.Printf("%T", myChannel)
}
執行結果:
chan bool
利用 channel變數 <- 要傳的值
這個方法即可將值傳到 channel 上
package main
//import "fmt"
func foo(myChannel chan string){
myChannel <- "封印解除!!"
}
func main(){
myChannel := make(chan string)
go foo(myChannel)
}
執行結果:
(什沒都沒有)
利用 要接收的變數 <- channel變數
就可以取得 channel 上的值了,要注意的一點是,「取得channel上的值」這個步驟會進行等待,一定要有值傳到 channel 上,才會接收,如果接收不到就會一直等待!
package main
import "fmt"
func foo(myChannel chan string){
myChannel <- "封印解除!!"
}
func main(){
myChannel := make(chan string)
go foo(myChannel)
receiver := <- myChannel
fmt.Println(receiver)
}
執行結果:
封印解除!!
接收 channel 的值,不一定要真的有變數去接
就好比函式的回傳值不一定要有變數去接一樣,接收 channel 的值時不一定真的要有變數去接
package main
import "fmt"
func foo(myChannel chan string){
fmt.Println("呼叫 foo()")
myChannel <- "封印解除!!"
}
func main(){
myChannel := make(chan string)
go foo(myChannel)
// 等待接收 channel
// 不一定要有變數去接球
<- myChannel
}
執行結果:
呼叫 foo()
package main
import "fmt"
func foo(foo_is_end chan bool){
for i := 0; i < 5; i = i + 1{
fmt.Println("副執行緒")
}
// 傳送 true 到 channel 上
foo_is_end <- true
}
func main(){
// 新增一個 channel
foo_is_end := make(chan bool)
// 將該 channel 傳給 foo()
go foo(foo_is_end)
for i := 0; i < 5; i = i + 1{
fmt.Println("主執行緒")
}
// 等待 channel 有值出現
<- foo_is_end
}
執行結果:
主執行緒
主執行緒
主執行緒
主執行緒
主執行緒
副執行緒
副執行緒
副執行緒
副執行緒
副執行緒
執行結果每次都不一樣,由作業系統決定
如同前面所說,在接收 channel 上的值時,會進行等待,但如果根本沒有值會被發送上來,那麼,很直覺地,程式就會一直等下去囉
package main
import "fmt"
func foo(foo_is_end chan bool){
for i := 0; i < 5; i = i + 1{
fmt.Println("副執行緒")
}
// 註解掉,不把值傳送到 channel 上
// foo_is_end <- true
}
func main(){
// 新增一個 channel
foo_is_end := make(chan bool)
// 將該 channel 傳給 foo()
go foo(foo_is_end)
for i := 0; i < 5; i = i + 1{
fmt.Println("主執行緒")
}
// 等待 channel 有值出現
// 但永遠等不到
<- foo_is_end
}
執行結果:
主執行緒
主執行緒
主執行緒
主執行緒
主執行緒
副執行緒
副執行緒
副執行緒
副執行緒
副執行緒
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan receive]:
main.main()
C:/Users/liao2/OneDrive/go-tutorial/lesson16b.go:22 +0xea
exit status 2
人生第一個 deadlock GET!
deadlock 就是死鎖,簡單來說就是有執行緒卡住不能往前進了。而這個範例被卡住的就是主執行緒,因為它一直苦苦等著 channel 上的值出現
本文圖片大多來自:
庫洛魔法使第一季第廿六集