channelchannel當然也算一種并發容器,其本質上是無鎖隊列 。
需要注意兩點:
- 為了在多讀多寫條件下維持隊列的數據結構,通常通過CAS+自旋等待來操作關鍵數據 。
同樣的,channel沒有那么快 。要避免在劇烈競爭的環境下使用channel 。
- 通常會使用channel來做生產者-消費者模式的并發結構 。數據數據可以按照一定的規律分區,則可以考慮每個消費者對應一個channel,然后生產者根據數據的key來決定放到哪個channel 。這樣本質上減緩了鎖的競爭 。
var once sync.Oncevar globalXXX *XXXfunc GetXXX() *XXX{ once.Do(func(){ globalXXX = getXXX() }) return globalXXX}不安全代碼string與[]byte的轉換string與slice的結構本質上是一樣的,可以直接強制轉換:
import ( "reflect" "unsafe")// copy from prometheus source code// NoAllocString convert []byte to stringfunc NoAllocString(bytes []byte) string { return *(*string)(unsafe.Pointer(&bytes))}// NoAllocBytes convert string to []bytefunc NoAllocBytes(s string) []byte { strHeader := (*reflect.StringHeader)(unsafe.Pointer(&s)) sliceHeader := reflect.SliceHeader{Data: strHeader.Data, Len: strHeader.Len, Cap: strHeader.Len} return *(*[]byte)(unsafe.Pointer(&sliceHeader))}上面的代碼可以避免string和[]byte在轉換的時候發生拷貝 。
注意:轉換后的對象一定要立即使用,不要進一步引用到更深的層次中去 。牢記這是不安全代碼,謹慎使用 。強制類型轉換懂C的人,請繞過……
例如一個[]int64的數組要轉換為[]uint64的數組,使用個指針強制轉換就行了 。
package mainimport ( "testing" "unsafe")func TestConvert(t *testing.T) { int64Slice := make([]int64, 0, 100) int64Slice = append(int64Slice, 1, 2, 3) uint64Slice := *(*[]uint64)(unsafe.Pointer(&int64Slice)) t.Logf("%+v", uint64Slice)}還有一種使用場景,要比較兩個大數組是否完全一樣:可以把數組強制轉換為[]byte,然后使用bytes.Compare() 。相當于C中的memcmp()函數 。
類似的操作還很多,推薦這篇文章:《深度解密Go語言之unsafe》
模糊記得一個golang(或是rust)的原則:普通開發者可以使用安全代碼來無顧慮的使用,高手把不安全代碼包裝成安全代碼來提供高性能組件 。數組越界檢查的開銷相比C的數組訪問,為什么golang可以做到很安全?
答案是編譯器加了兩條越界檢查的指令 。每次通過下標訪問數組,就像這樣:
if index<0 || index>=len(slice){ panic("out of index")}return slice[index]這兩條越界檢查指令是有開銷的,請看我的測試:《golang中數組邊界檢查的開銷大約是1.87%~3.12%》
所以,當某些位置使用類似查表法的時候,可以用不安全代碼繞過越界檢查:
slice := make([]byte, 1024*1024)offset = 100b := (*(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&arr[0])) + uintptr(offset))))編譯/鏈接階段使用盡量新的golang版本理論上,每個新版的golang,都有一定編譯器優化的提升 。
經驗總結擴展閱讀
- 持續集成指南:GitLab 的 CI/CD 工具配置與使用
- 信用卡怎么查消費明細
- 消費貸款申請產生的費用高嗎
- oppo賬號的姓名怎么修改
- 租房可以換鎖芯嗎合法么 租房換鎖費用誰來承擔
- 特斯拉可以用家用電充電嗎
- 拆線多久可以用祛疤膏?
- 衛生間和廚房用什么瓷磚?
- 肉蓯蓉副作用是什么
- 計米器怎么設置參數
