- channel的一对一会阻塞,添加buffer不会阻塞。
func GetResponse() string {// 如果是这一句,确实只返回了1个,但是其他几个都阻塞了,浪费协程,浪费服务器资源,容易造成泄露等安全问题// ch := make(chan string)// 如果用下面这句,每个协程都只管往channel中传数据,传完就结束ch := make(chan string, 10)for i := 0; i < 10; i++ {go func(idx int) {time.Sleep(time.Millisecond * 10)ch <- "response from " + strconv.Itoa(idx)}(i)}return <-ch// 如果要返回所有任务的结果,就在这里for循环拼接拿到的所有<-ch数据
}func TestGetAnyResponse(t *testing.T) {fmt.Println("before: goroutine number is ", runtime.NumGoroutine()) // 2fmt.Println(GetResponse())time.Sleep(time.Second * 1)fmt.Println("afer: goroutine number is ", runtime.NumGoroutine()) // 第一种输出11,第二种输出2
}
- buffered Channel实现对象池。
type ReuseableObj struct {num int
}
type ObjPool struct {bufferChan chan *ReuseableObj
}func NewObjPool(num int) *ObjPool {objPool := ObjPool{}objPool.bufferChan = make(chan *ReuseableObj, num)for i := 0; i < num; i++ {rObj := ReuseableObj{num: i}objPool.bufferChan <- &rObj}return &objPool
}func (p *ObjPool) GetObj(timeout time.Duration) (*ReuseableObj, error) {select {case ret := <-p.bufferChan:return ret, nildefault:return nil, errors.New("time out")}
}func (p *ObjPool) ReleaseObj(obj *ReuseableObj) error {select {case p.bufferChan <- obj:return nildefault:return errors.New("overflow")}
}func TestChannelPool(t *testing.T) {objPool := NewObjPool(10)for i := 0; i < 20; i++ {if v, err := objPool.GetObj(time.Second * 1); err != nil {fmt.Println(err)} else {fmt.Printf("adderss is %x\n", unsafe.Pointer(&v))fmt.Println(v.num)if msg := objPool.ReleaseObj(v); msg != nil {fmt.Println(msg)}}}
}
sync.Pool
的介绍。
- 获取时先去私有对象中获取,如果不存在就在相同Processor中的共享池中获取,如果还没有,则去其他Processor中去获取。
- 存放时,如果私有对象不存在,就放在私有对象中,如果存在就放在Processor中。
- 它其实不能当做对象池去用,因为每次GC都会清空
sync.Pool
中所有对象,所以这就是为什么上面我们用buffered channel当做对象池来用。 - 那如果被GC清空了,还要获取的话,怎么办?
sync.Pool
就会重新初始化一下。 - 其实,每次如果没有对象,则会开启New方法初始化一下,所以获取的就是999,这里暂时不需要断言了,因为返回的是
any
。 - 所以,它不适合用来管理自己要定义生命周期的池,因为系统GC不可控。
func TestSyncPool(t *testing.T) {pool := &sync.Pool{New: func() any {fmt.Println("create...")return 999},}t.Log(pool.Get()) // 999t.Log(pool.Get()) // 999t.Log(pool.Get()) // 999pool.Put(100)t.Log(pool.Get()) // 100t.Log(pool.Get()) // 999pool.Put(98)pool.Put(99)t.Log(pool.Get()) //98t.Log(pool.Get()) //99pool.Put(100)runtime.GC()t.Log(pool.Get()) //999
}
-
我们之前一直在用单元测试在写代码,正常保证文件名
_test.go
和方法Testxxx
即可。在测试代码中如果用t.Fail("xxx")
代码其实还会一直执行下去;如果用t.Fatal("xxx")
则代码直接中断了。当然还可以借助第三方包进行断言操作。 -
benchmark和test使用类似,有自己定义的一套语言,如下。
func BenchmarkConcatString(b *testing.B) {b.ResetTimer()for i := 0; i < 10000; i++ {}b.StopTimer()
}
// 输出信息包括一共运行次数,每次op用的时间,每次op用到的allocs次数,可以用来比较查找问题
BenchmarkConcatString
BenchmarkConcatString-16
1000000000 0 B/op 0 allocs/op
还有一些BDD的框架,比如goconvey
。
- 使用反射编程。主要是是
reflect
的几个方法,主要的是如果是获取变量值或者调用方法则后面是ValueOf()
,如果是获取信息的则用TypeOf()
。
func TestReflect(t *testing.T) {c := &Course{name: "math", score: 100}// 直接访问成员变量:ValueOf + FieldByNamet.Log(reflect.ValueOf(*c).FieldByName("score")) //100t.Log(c) //&{math 100}// 访问成员方法:ValueOf + MethodByNamereflect.ValueOf(c).MethodByName("UpdateScore").Call([]reflect.Value{reflect.ValueOf(60)})t.Log(c) //&{math 60}// 获取成员变量的一些信息:TypeOf + FieldByNameif nameField, ok := reflect.TypeOf(*c).FieldByName("name"); ok {t.Log(nameField.Tag.Get("format")) // haha} else {t.Log("get error")}if nameField1, ok := reflect.TypeOf(*c).FieldByName("score"); ok {t.Log(nameField1.Type) // int} else {t.Log("get error")}
}
有一个小知识点,我们的map是不能相互比较的,但可通过reflect.DeepEqual(map1, map2)
实现比较。
func TestCmpMap(t *testing.T) {map1 := map[int]string{1: "one", 2: "two"}map2 := map[int]string{1: "one", 2: "two"}//t.Log(map1 == map2)t.Log(reflect.DeepEqual(map1, map2)) //true
}
反射编程灵活性高,但性能有损耗,代码可读性也会降低。
-
unsafe
尽量不要使用,除非有些地方必要要使用这种变量类型。 -
自带的
json
解析不如第三方解析包,比如easyjson
。但我尝试了一下,还是放弃easyjson
,有些麻烦,写个文件,然后使用命令生成个特殊文件里面自动生成好了针对这个对象的marshal
等方法,意味着可以直接调用。我还是乖乖使用自带的吧。
type Student struct {Name string `json:"name"`Age int `json:"age"`
}var studentStr = `{"name": "Eric","age": 18
}`func TestJson(t *testing.T) {s := new(Student)if err := json.Unmarshal([]byte(studentStr), s); err != nil {t.Log(err)} else {t.Log(s.Name) //Eric}if data, err := json.Marshal(s); err != nil {t.Log(err)} else {t.Log(string(data)) //{"name":"Eric","age":18},data是个bytes[]需要转换}
}func TestEnCoderAndDecoder(t *testing.T) {s := new(Student)decoder := json.NewDecoder(strings.NewReader(studentStr))if err := decoder.Decode(s); err != nil {t.Log(err)} else {t.Log(s.Name) //Eric}encoder := json.NewEncoder(os.Stdout)if err := encoder.Encode(s); err != nil { // 直接输出了{"name":"Eric","age":18}t.Log(err)}
}
- 用内置的
http
可以开启服务。
package mainimport ("fmt""net/http"
)func main() {http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "hello http...")})http.HandleFunc("/getName", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("Eric"))})http.ListenAndServe(":9090", nil)
}
可以使用第三方路由。
// 安装
go get -u github.com/julienschmidt/httprouter
// 使用GET和POST以及接收参数,POST可以分别接收Form参数和Json参数都是从Request中获取
package mainimport ("encoding/json""fmt""log""net/http""github.com/julienschmidt/httprouter"
)func GetByIdFunc(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {fmt.Fprint(w, "hello http...")
}func GetByNameFunc(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {w.Write([]byte("hello " + ps.ByName("name")))
}type User struct {Id stringName string
}func main() {router := httprouter.New()router.GET("/", GetByIdFunc)router.GET("/getByName/:name", GetByNameFunc)router.POST("/submitForm", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {r.ParseForm()w.Write([]byte("hello id:" + r.Form.Get("id") + ";name:" + r.Form.Get("name")))})router.POST("/submitJson", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {content := make([]byte, r.ContentLength)r.Body.Read(content)user := new(User)if err := json.Unmarshal(content, user); err == nil {w.Write([]byte("hello id:" + user.Id + ";name:" + user.Name))} else {fmt.Print(err)w.Write([]byte("error happened"))}})log.Fatal(http.ListenAndServe(":9090", router))
}
- 性能分析。
- 性能分析通过一些内置工具和第三方工具来分析
pprof
,profile
,graphviz
,还可以通过web界面查看。具体参考GO 性能分析。 - 一些常用指标比如
wall time
,CPU time
,Block time
,Memory allocation
,GC times
等等。 - 性能差的原因之一,有可能是代码中使用了不恰当的锁。
- 编程习惯。
- 复杂对象(比如数组或者结构体)尽量传递引用。影响系统GC。
- 初始化时就制定合适的容器大小,避免自动扩容。
- string拼接,有个strings.Builder。
func TestStringBuilder(t *testing.T) {var sb strings.Buildersb.WriteString("Hi,")sb.WriteString("Eric")t.Log(sb.String()) //Hi,Eric
}
- 注意僵尸进程
- 考虑使用池优化资源使用
- 不要害怕错误,尽早抛出错误,给出回应
入门之路结束!切换进阶之路!