• Go语言入门记录:从channel的池应用、sync的Pool、benchmark、反射reflect、json处理、http、性能分析和一些编程习惯


    1. 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()) // 2
    	fmt.Println(GetResponse())
    	time.Sleep(time.Second * 1)
    	fmt.Println("afer: goroutine number is ", runtime.NumGoroutine()) // 第一种输出11,第二种输出2
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    1. 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, nil
    	default:
    		return nil, errors.New("time out")
    	}
    }
    
    func (p *ObjPool) ReleaseObj(obj *ReuseableObj) error {
    	select {
    	case p.bufferChan <- obj:
    		return nil
    	default:
    		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)
    			}
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    1. 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()) // 999
    	t.Log(pool.Get()) // 999
    	t.Log(pool.Get()) // 999
    	pool.Put(100)
    	t.Log(pool.Get()) // 100
    	t.Log(pool.Get()) // 999
    	pool.Put(98)
    	pool.Put(99)
    	t.Log(pool.Get()) //98
    	t.Log(pool.Get()) //99
    	pool.Put(100)
    	runtime.GC()
    	t.Log(pool.Get()) //999
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    1. 我们之前一直在用单元测试在写代码,正常保证文件名_test.go和方法Testxxx即可。在测试代码中如果用t.Fail("xxx")代码其实还会一直执行下去;如果用t.Fatal("xxx")则代码直接中断了。当然还可以借助第三方包进行断言操作。

    2. 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    还有一些BDD的框架,比如goconvey

    1. 使用反射编程。主要是是reflect的几个方法,主要的是如果是获取变量值或者调用方法则后面是ValueOf(),如果是获取信息的则用TypeOf()
    func TestReflect(t *testing.T) {
    	c := &Course{name: "math", score: 100}
    	// 直接访问成员变量:ValueOf + FieldByName
    	t.Log(reflect.ValueOf(*c).FieldByName("score")) //100
    	t.Log(c)                                        //&{math 100}
    	// 访问成员方法:ValueOf + MethodByName
    	reflect.ValueOf(c).MethodByName("UpdateScore").Call([]reflect.Value{reflect.ValueOf(60)})
    	t.Log(c) //&{math 60}
    	// 获取成员变量的一些信息:TypeOf + FieldByName
    	if 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")
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    有一个小知识点,我们的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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    反射编程灵活性高,但性能有损耗,代码可读性也会降低。

    1. unsafe尽量不要使用,除非有些地方必要要使用这种变量类型。

    2. 自带的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)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    1. 用内置的http可以开启服务。
    package main
    
    import (
    	"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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    可以使用第三方路由。

    // 安装
    go get -u github.com/julienschmidt/httprouter
    // 使用GET和POST以及接收参数,POST可以分别接收Form参数和Json参数都是从Request中获取
    package main
    
    import (
    	"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   string
    	Name 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))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    1. 性能分析。
    • 性能分析通过一些内置工具和第三方工具来分析pprofprofilegraphviz,还可以通过web界面查看。具体参考GO 性能分析
    • 一些常用指标比如wall timeCPU timeBlock timeMemory allocationGC times等等。
    • 性能差的原因之一,有可能是代码中使用了不恰当的锁。
    1. 编程习惯。
    • 复杂对象(比如数组或者结构体)尽量传递引用。影响系统GC。
    • 初始化时就制定合适的容器大小,避免自动扩容。
    • string拼接,有个strings.Builder。
    func TestStringBuilder(t *testing.T) {
    	var sb strings.Builder
    	sb.WriteString("Hi,")
    	sb.WriteString("Eric")
    	t.Log(sb.String()) //Hi,Eric
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 注意僵尸进程
    • 考虑使用池优化资源使用
    • 不要害怕错误,尽早抛出错误,给出回应

    入门之路结束!切换进阶之路!

  • 相关阅读:
    基于TypeScript和egret游戏开发引擎星空大作战游戏设计
    SK 简化流行编程语言对 生成式AI 应用开发的支持
    【Django】一些DRF的学习记录
    【java学习】面向对象特征之一:封装和隐藏(23)
    rpc网络
    PR BeatEdit 节奏卡点神器 的报错 beat detection error: IBT failed 和解决路径
    window server事件ID说明
    通关GO语言04 集合类型:如何正确使用 array、lice 和 map?
    Springboot毕设项目基于JavaWeb的模玩网站系统odk84(java+VUE+Mybatis+Maven+Mysql)
    linux Tomcat使用APR协议 提高性能
  • 原文地址:https://blog.csdn.net/wsb200514/article/details/132527392