go dosomething() //走,兄弟咱們搞點事情
func say(s string) { fmt.Printf("%s say\n", s) } func main() { go say("lisi") say("zhangsan") }
執行結果html
zhangsan say
上面的案例執行了2次say方法,但只有zhangsan執行成功了。緣由是由於lisi是開了一個goroutine去執行,還沒執行完但此時的main函數已經退出了。程序員
lisi估計是有點害羞,說話語速比較慢,所以咱們要等lisi一下,拋開串行執行和sleep外咱們用一個消息管道類通知,這裏咱們就要zhangsan和lisi一塊兒說golang
func say(s string, c chan int) { fmt.Printf("%s say\n", s) c <- 1 //在消息管道里傳1,表明我已經說過了 } func main() { c := make(chan int) go say("lisi", c) go say("zhangsan", c) v1, v2 := <-c, <-c fmt.Printf("lisi:%d , zhangsan:%d\n", v1, v2) }
執行結果以下,固然也有可能lisi say 在zhangsan say的前面,等於1表明他倆都說過話了express
zhangsan say lisi say lisi:1 , zhangsan:1
過程分解
一、建立一個無緩衝的channel
二、異步執行 go say("lisi", c)
三、異步執行 go say("zhangsan", c)
四、假設zhangsan先執行,那麼zhangsan的1先放入管道c,若是這時候正好lisi在執行,很差意思管道c只有1個長度放不下了。此時lisi: c<- 1阻塞
五、v1 := <- c 執行,zhangsan的值1從管道里拿出來了。
六、lisi執行 c <- 1
七、v2 := <- c執行,lisi的值1也從管道里拿出來了
八、執行fmt.Printf編程
併發編程就這麼用的,不過你們發現問題沒有,過程分解步驟4有阻塞,同一時刻5和7也是阻塞的(等待管道里拿值遲遲拿不到)
適當改版一下,以下:c#
func say(s string, c chan int) { fmt.Printf("%s say\n", s) c <- 1 //在消息管道里傳1,表明我已經說過了 } func main() { c := make(chan int, 2) //改動點,管道長度設成了2 go say("lisi", c) go say("zhangsan", c) v1, v2 := <-c, <-c fmt.Printf("lisi:%d , zhangsan:%d\n", v1, v2) }
這時候的過程分解
一、建立一個緩衝爲2的channel
二、異步執行 go say("lisi", c)
三、異步執行 go say("zhangsan", c)
四、假設zhangsan先執行,那麼zhangsan的1先放入管道c,若是這時候正好lisi在執行,lisi的1也放入管道c
五、v1 := <- c 執行,zhangsan的值1從管道里拿出來了。
六、v2 := <- c執行,lisi的值1也從管道里拿出來了
七、執行fmt.Printf安全
理論上來講應該是少了一步,實際狀況可能會更好一些,由於步驟4沒有阻塞(也就是zhangsan和lisi的值1能夠同時放進去)。網絡
步驟5和6雖然有阻塞(這裏的阻塞跟c#裏的await是一個意思),可是管道c一旦有值會立馬拿出來,等v1和v2都有值了而後執行fmt.Printf併發
func say(s string) int { fmt.Printf("%s say\n", s) return 1 } func main() { msg:= go say("lisi", c) //PS:這裏會報錯syntax error: unexpected go, expecting expression }
仍是看代碼吧異步
package main import ( "fmt" ) //學生結構體(實體) type Stu struct { Name string Age int } func say(name string) Stu { fmt.Printf("%s say\n", name) stu := Stu{Name: name, Age: 18} return stu } func main() { c := make(chan int) go func() { stu := say("lisi") //返回一個學生實體 fmt.Printf("我叫%s,年齡%d\n", stu.Name, stu.Age) c <- 1 //信號位表示調用完畢 }() fmt.Println("go func") <-c fmt.Println("end") }
執行結果:
go func
lisi say
我叫lisi,年齡18
end
func say(s string, c chan int) { fmt.Printf("%s say\n", s) //c <- 1 這裏原本應該給c管道傳值的,結果沒傳 } func main() { c := make(chan int) go say("lisi", c) v1 := <-c //這裏會一直阻塞,致使死鎖 fmt.Printf("lisi:%d\n", v1) //前面死鎖,這裏沒法輸出 }
執行報錯內容:
fatal error: all goroutines are asleep - deadlock!
goroutine也叫協程是一種輕量級別用戶空間線程,不受操做系統的調度,因此須要用戶自行調度(通常是加鎖和信道),協程能作的事情進程和線程一樣能作。進程和線程的切換主要依賴於時間片的控制,而協程的切換則主要依賴於自身,這樣的好處是避免了無心義的調度,由此能夠提升性能,但也所以,程序員必須本身承擔調度的責任
什麼是協程:from百科
協程與子例程同樣,協程(coroutine)也是一種程序組件。相對子例程而言,協程更爲通常和靈活,但在實踐中使用沒有子例程那樣普遍。協程源自 Simula 和 Modula-2 語言,但也有其餘語言支持ps:子例程是某個主程序的一部分代碼
goroutine能夠看做是協程的go語言實現,它是語言原生支持的,相對於通常由庫實現協程的方式,goroutine更增強大,它的調度必定程度上是由go運行時(runtime)管理。其好處之一是,當某goroutine發生阻塞時(例如同步IO操做等),會自動出讓CPU給其它goroutine。
後面會單獨的在介紹進程、線程、協程以前的關係,也能夠參考如下幾篇文章
channel是Go語言在語言級別提供的goroutine間的通訊方式。咱們可使用channel在兩個或 多個goroutine之間傳遞消息。channel是進程內的通訊方式,所以經過channel傳遞對象的過程和調用函數時的參數傳遞行爲比較一致,好比也能夠傳遞指針等。若是須要跨進程通訊,咱們建議用 分佈式系統的方法來解決,好比使用Socket或者HTTP等通訊協議。Go語言對於網絡方面也有很是完善的支持。 channel是類型相關的。也就是說,一個channel只能傳遞一種類型的值,這個類型須要在聲明channel時指定。若是對Unix管道有所瞭解的話,就不難理解channel,能夠將其認爲是一種類 型安全的管道。
關於channel有必要詳細瞭解下。能夠參考
golang的channel使用
package main import ( "fmt" "time" ) var sum int = 0 func todo(i int, c chan int) { //c <- 1 //執行一次放一個值1 c <- i //把i的值放進去 } func getSum(count int, c chan int, ce chan int) { for i := 0; i <= count; i++ { sum += <-c // k, isopen := <-c // if !isopen { // fmt.Printf("channel is close") // break // } else { // fmt.Printf("sum:%d,k:%d\n", sum, k) // sum += k // } } ce <- 1 } func main() { count := 100000 //10W個goroutine c := make(chan int, count) //有緩衝channel ce := make(chan int) //計算getSum信號量 //開始計時 begin := time.Now() fmt.Println("開始時間:", begin) for i := 0; i <= count; i++ { go todo(i, c) } //再開一個goroutine去計算channel裏的值求Sum go getSum(count, c, ce) <-ce //這裏是getSum方法執行結束信號量 end := time.Now() fmt.Println("結束時間:", end, time.Since(begin)) fmt.Println(sum) }
硬件信息
環境:THINKPAD L460、WIN7x6四、8G內存、i5-6200U 2.3GHz 雙核4線程
語言:LiteIDE X3三、golang 1.9.2
屢次執行結果:38.5ms - 51ms之間
把 c := make(chan int, count) 改成 c := make(chan int) 改爲無緩衝
c := make(chan int) //重點,這裏改爲無緩衝的
屢次執行結果:304-325ms之間
class Program { private static readonly object obj = new object(); static void Main(string[] args) { DateTime begin = DateTime.Now; long sum = 0; Parallel.For(1, 100001, (i) => { lock (obj) { sum += i; } }); TimeSpan ts = DateTime.Now - begin; Console.WriteLine($"{sum},耗時:{ts.TotalMilliseconds}ms"); Console.ReadLine(); } }
運行結果 : 大約在90-120ms左右,雖然數值上差了2倍左右,其實差異並非很大,也沒有直接的可比性,由於線程和協程並非一個數量級別,上面goroutine用到了channel通道,net core 用的lock鎖,所以僅供參考。整體看來.net core的性能總體仍是蠻高的
PS:題外話 其實c#裏也有協程"fiber",網上資料比較少了解很少。