測試數據:單協程操作1億數據,以及多協程(10條協程)操作1億數據(每條協程操作1kw數據)
廢話少說,貼代碼:
單協程測試運算:
package main
import (
"fmt"
"time"
)
func testNum(num int) {
for i := 1; i <= 10000000; i++{
num = num + i
num = num - i
num = num * i
num = num / i
}
}
func main() {
start := time.Now()
for i := 1; i <= 10; i++ {
testNum(1)
}
end := time.Now()
fmt.Println(end.Sub(start).Seconds())
}
運行時間為:0.065330877
多協程測試運算:
package main
import (
"fmt"
"time"
"sync"
)
var synWait sync.WaitGroup
func testNum(num int) {
for i := 1; i <= 10000000; i++{
num = num + i
num = num - i
num = num * i
num = num / i
}
synWait.Done() // 相當於 synWait.Add(-1)
}
func main() {
start := time.Now()
for i := 1; i <= 10; i++ {
synWait.Add(1)
go testNum(1)
}
synWait.Wait()
end := time.Now()
fmt.Println(end.Sub(start).Seconds())
}
運行時間為:0.019804929
比較結果,和預期的是一樣,多協程要比單協程處理數據快,很多人還會去設置runtime.GOMAXPROCS(x),其實
這是遠古程序員的做法了,因為go 1.6以上的版本就已經會自動根據計算機核的調用啦!!!
如果沒有調用runtime.GOMAXPROCS 去設置CPU,Golang默認使用所有的cpu核。
以下是以map來做實驗,為了測試准確性,統一都加鎖
單協程/多協程測試map:
package main
import (
"fmt"
"time"
"sync"
)
var synWait sync.WaitGroup
var normalMap map[int]int
var synMutex sync.Mutex
func testNum(num int) {
for i := 1; i <= 10000000; i++{
synMutex.Lock()
normalMap[i] = num // testslic = append(testslic, num) 這里slice也是一樣的道理
synMutex.Unlock()
}
synWait.Done() // 相當於 synWait.Add(-1)
}
func main() {
normalMap = make(map[int]int)
start := time.Now()
for i := 1; i <= 10; i++ {
synWait.Add(1)
testNum(1) // 單協程操作
//go testNum(1) // 多協程並發操作
}
synWait.Wait()
end := time.Now()
fmt.Println(end.Sub(start).Seconds())
}
單協程操作 testNum(1), 運行時間為:19.101255922
多協程操作 go testNum(1), 運行時間為:28.210580532
是不是出乎意料!!! 多協程操作map反而慢,這說明map這個數據結構對並發操作效率比較低,如果在保證線性安全的前提下
盡量單協程去操作map,如果上面代碼注釋掉加鎖,單協程操作就更快了, 運行時間為:16.307839364
原因為什么呢???這篇博客有所闡述:https://www.cnblogs.com/ipub520/p/7718905.html
協程通道測試map:
package main
import (
"fmt"
"time"
"sync"
)
var synWait sync.WaitGroup
var normalMap map[int]int
func testNum(data chan int, num int) {
for i:=1;i<=10000000;i++{
data <- i
}
synWait.Done()
}
func main() {
normalMap = make(map[int]int)
data := make(chan int)
start := time.Now()
go concurrent(data)
for i := 1; i <= 10; i++ {
synWait.Add(1)
go testNum(data,1) // 多協程並發操作
}
synWait.Wait()
end := time.Now()
fmt.Println(end.Sub(start).Seconds())
}
func concurrent(data chan int) {
for {
i := <- data
normalMap[i] = i
}
}
運行時間為:53.554329275
通道內部實現也是加鎖,這肯定是要比純用鎖慢一點的,這也正好驗證了(網上有些人說通道要比加鎖快,這是錯誤的)。但是使用通道是golang的一種哲學意義,規定了入口,里面的數據
結構就不要再擔憂,是否要加鎖了,因為全部都是安全的(可以避免很多bug,畢竟程序大部分問題還是出自程序員的邏輯代碼),還是那句話不要通過共享內存來通信,而要通過通信來共享內存!
slice也是和map差不多的,並發append的時候也必須加鎖
我們舉一個簡單例子,比如,當A和B兩個協程運行append的時候同時發現s[1]這個位置是空的,他們就都會把自己的值放在這個位置,這樣他們兩個的值就會覆蓋,造成數據丟失。
總結一下吧:(map性能 單協程 > 多協程 > 通道 )
多協程去運算確實快比單協程要快,因為golang會默認根據多核去跑,但是如果操作涉及到加鎖的時候(例如map,slice),就要注意,並發操作效率不及單協程(這點和erlang操作ets不一樣,erlang恰好相反)。
