Concurrency is the capability to deal with lots of things at once.
簡單的解釋,一個在跑步的人,因為鞋帶鬆了,所以他停下來綁鞋帶,綁完後繼續跑。
concurrency 只能在單一 CPU 核裡執行
Parallelism
Parallelism is doing lots of things at the same time. It might sound similar to concurrency but its actually different.
同樣用慢跑來解釋的話,在慢跑的人,同時在用耳機聽音樂,在同一時間做了很多事。
Parallelism 可以同時多核處理
總結
用 CPU 來解釋
If the person is doing running on 1 core and tying his laces on another core its Parallelism. If he is running on on 1 core and then switching/stopping to tie his laces on the same core then its concurrent
一個 threads 可能就會有幾千個 goroutine,因此開 OS thread 的量會比較少
當 thread 被阻塞時,可以開新 thread 並將剩餘的 goroutine 轉移過去
goroutine 最多運行 GOMAXPROCS 數量(可以設定)
main() 也是一個 goroutine 稱為 main Goroutine
Goroutine 可以透過 channel 來進行溝通,防止同時訪問共享的資源,造成競爭
syntax
12
// keyword gogohello()
start a Goroutine
123456789101112131415161718192021
packagemainimport("fmt""time")funchello(){fmt.Println("Hello world goroutine")}funcmain(){gohello()time.Sleep(1*time.Second)fmt.Println("main function")}// Hello world goroutine// main function// 因為 hello() 進入 goroutine,但是 main() 已經結束,因此所有的 goroutine 都會直接打斷,程序退出。// 加入 sleep 1 秒,讓 hello() 有足夠的時間 retuen 回來,1秒後已經先 return 回來再顯示 "main function"
Starting multiple Goroutines
123456789101112131415161718192021222324252627
packagemainimport("fmt""time")funcnumbers(){fori:=1;i<=5;i++{time.Sleep(250*time.Millisecond)fmt.Printf("%d ",i)}}funcalphabets(){fori:='a';i<='e';i++{time.Sleep(400*time.Millisecond)fmt.Printf("%c ",i)}}funcmain(){gonumbers()goalphabets()time.Sleep(3000*time.Millisecond)fmt.Println("main terminated")}// 1 a 2 3 b 4 c 5 d e main terminated
Channels can be thought as pipes using which Goroutines communicate. Similar to how water flows from one end to another in a pipe, data can be sent from one end and received from the another end using channels.
Slice: The size specifies the length. The capacity of the slice is
equal to its length. A second integer argument may be provided to
specify a different capacity; it must be no smaller than the
length. For example, make([]int, 0, 10) allocates an underlying array
of size 10 and returns a slice of length 0 and capacity 10 that is
backed by this underlying array.
Map: An empty map is allocated with enough space to hold the
specified number of elements. The size may be omitted, in which case
a small starting size is allocated.
Channel: The channel’s buffer is initialized with the specified
buffer capacity. If zero, or the size is omitted, the channel is
unbuffered.
12
varachaninta:=make(chanint)
123456789101112131415
packagemainimport"fmt"funcmain(){varachanintifa==nil{fmt.Println("channel a is nil, going to define it")a=make(chanint)fmt.Printf("Type of a is %T",a)}}// channel a is nil, going to define it// Type of a is chan int
send、receive、close
123
data:=<-ch// read from channel ch<-ch// read from channel chch<-data// write to channel ch
Sends and receives are blocking by default
When a data is sent to a channel, the control is blocked in the send statement until some other Goroutine reads from that channel.
Similarly when data is read from a channel, the read is blocked until some Goroutine writes data to that channel.
如果另一方一直對沒有動作,會造成 Deadlock
將上面範例 sleep 改用 channel 改寫
<-done 這行會導致 main goroutine blocked 在這邊,直到其他 goroutine 將 data 寫入 done,不然是不會繼續往下走,也意味著就不需要用 sleep 來停止
123456789101112131415161718192021222324252627
packagemainimport("fmt""time")// 接收 bool 的 cahnnelfunchello(donechanbool){fmt.Println("hello go routine is going to sleep")time.Sleep(4*time.Second)fmt.Println("hello go routine awake and going to write to done")done<-true}funcmain(){//用 make 建立一個不為 nil 的 channeldone:=make(chanbool)fmt.Println("Main going to call hello go goroutine")gohello(done)// 這邊 done channel,沒有任何東西,因此被 blockeds 住,等有東西到 channel 才會繼續下一行<-donefmt.Println("Main received data")}// Main going to call hello go goroutine// hello go routine is going to sleep// hello go routine awake and going to write to done// Main received data
<-done 會等到 channel data 回來才會繼續執行下一行
<-done 左邊並沒有任何 variable 去接收,因為這邊只是會了要讓他先執行 hello() 並不是要回傳的 value
packagemainimport"fmt"// send only channelfuncsendData(sendchchan<-int){// 單向 channelsendch<-10}funcmain(){chnl:=make(chanint)// 雙向 channelgosendData(chnl)fmt.Println(<-chnl)}// 10
將單向改成雙向,並且透過 func 來控制單向
Closing channels and for range loops on channels
sender 可以關閉 channel 已告知 receivers,已經沒有 data
只有 sender 要關閉 channel,如果沒有 close channel 可能會導致 panic fatal error: all goroutines are asleep - deadlock!
Channels aren’t like files; you don’t usually need to close them. Closing is only necessary when the receiver must be told there are no more values coming, such as to terminate a range loop.
123456
v,ok:=<-ch// ok// true: 可以接收的狀態// false: 沒有任何 value & channel 已經關閉close(ch)// 關閉 channel,用 range 取出 channel 東西時,必須用 close() 告知已經沒東西了,否則會 deadlock
packagemainimport("fmt")funcproducer(chnlchanint){fori:=0;i<3;i++{chnl<-i}close(chnl)// range 就不需要這行去關閉}funcmain(){ch:=make(chanint)goproducer(ch)for{v,ok:=<-ch// 當沒有 close(chnl) 時,這邊就會產生 decklock,因為裡面並沒有 data 了ifok==false{break}fmt.Println("Received ",v,ok)}/* 用 range,當 channel close 會自動離開 for v := range ch { fmt.Println("Received ", v) } */}// Received 0 true// Received 1 true// Received 2 true// 沒有 close 會造成 fatal error: all goroutines are asleep - deadlock!
packagemainimport("fmt""time")funcwrite(chchanint){fori:=0;i<5;i++{ch<-ifmt.Println("successfully wrote",i,"to ch")}close(ch)}funcmain(){ch:=make(chanint,2)gowrite(ch)time.Sleep(2*time.Second)forv:=rangech{fmt.Println("read value",v,"from ch")// 另外如果沒有特別設定,就會一瞬間完成time.Sleep(2*time.Second)}}/*successfully wrote 0 to ch successfully wrote 1 to ch read value 0 from ch successfully wrote 2 to ch read value 1 from ch successfully wrote 3 to ch read value 2 from ch successfully wrote 4 to ch read value 3 from ch read value 4 from ch */
a worker pool is a collection of threads which are waiting for tasks to be assigned to them. Once they finish the task assigned, they make themselves available again for the next task.
A Mutex is used to provide a locking mechanism to ensure that only one Goroutine is running the critical section of code at any point of time to prevent race condition from happening.
123456
// Mutex is available in the sync packagemutex.Lock()x=x+1mutex.Unlock()// 同時間只會有一個 goroutine
Solving the race condition using mutex
1234567891011121314151617181920212223242526272829
packagemainimport("fmt""sync")varx=0funcincrement(wg*sync.WaitGroup,m*sync.Mutex){m.Lock()x=x+1m.Unlock()wg.Done()}funcmain(){varwsync.WaitGroupvarmsync.Mutexfori:=0;i<1000;i++{w.Add(1)goincrement(&w,&m)// 這裡一定要用 address}w.Wait()fmt.Println("final value of x",x)}// 必須在 local 跑// 1000// 如果沒加上 lock,同時就會有很多個 goroutine 在跑,導致最後結果不一樣
Solving the race condition using buffered channel
12345678910111213141516171819202122232425
packagemainimport("fmt""sync")varx=0funcincrement(wg*sync.WaitGroup,chchanbool){ch<-true// 當前面的 channel 還有東西,就會 block 住,導致後面無法繼續x=x+1// 因此同時只會有一個 goroutine 執行這行<-ch// 等這邊釋放出來後,才能夠繼續塞wg.Done()}funcmain(){varwsync.WaitGroupch:=make(chanbool,1)fori:=0;i<1000;i++{w.Add(1)goincrement(&w,ch)}w.Wait()fmt.Println("final value of x",x)}
packagemainimport("fmt""sync""time")// SafeCounter is safe to use concurrently.typeSafeCounterstruct{vmap[string]intmuxsync.Mutex}// Inc increments the counter for the given key.func(c*SafeCounter)Inc(keystring){c.mux.Lock()// Lock so only one goroutine at a time can access the map c.v.c.v[key]++c.mux.Unlock()}// Value returns the current value of the counter for the given key.func(c*SafeCounter)Value(keystring)int{c.mux.Lock()// Lock so only one goroutine at a time can access the map c.v.deferc.mux.Unlock()returnc.v[key]}funcmain(){c:=SafeCounter{v:make(map[string]int)}fori:=0;i<1000;i++{goc.Inc("somekey")}time.Sleep(time.Second)fmt.Println(c.Value("somekey"))}