本期看点(技巧类用【技】表示,易错点用【易】表示):
(1)goroutine控制并发数量的方式【技】
(2)Go发起HTTP请求【技】
(3)包循环依赖导致的异常【易】
正文如下:
1 goroutine控制并发数量的方式【技】
首先我们思考一个问题,为什么要控制goroutine的数量呢?
虽然goroutine的创建成本很低,而且占用的内存也很少,但是一旦数量没有控制,导致短时间内大量的goroutine同时执行也会造成内存崩溃、CPU占用率过高等问题,因此我们在生产级的项目中一定要注意控制好goroutine的数量,以免发生生产事故。
那么,我们都有哪些方式来控制goroutine的数量呢?
- sync.WaitGroup
- channel
- sync.WaitGroup+channel
- semaphore
(1)最简单的方式
func main() { group := sync.WaitGroup{} group.Add(3) for i := 0; i < 3; i++ { go func() { fmt.Println("hello...") group.Done() }() } group.Wait() }
这种方式非常的简单,但是弊端就是不容易灵活扩展
(2)sync.WaitGroup+channel方式
type Pool struct { queue chan int wg *sync.WaitGroup } func New(size int) *Pool { if size <= 0 { size = 1 } return &Pool{ queue: make(chan int, size), wg: &sync.WaitGroup{}, } } func (p *Pool) Add(delta int) { for i := 0; i < delta; i++ { p.queue <- 1 } for i := 0; i > delta; i-- { <-p.queue } p.wg.Add(delta) } func (p *Pool) Done() { <-p.queue p.wg.Done() } func (p *Pool) Wait() { p.wg.Wait() }
测试:
func main() { pool := pool.New(10) for i := 0; i < 100; i++ { pool.Add(1) go func() { time.Sleep(time.Second) fmt.Printf("%d hello...\n", i) pool.Done() }() } pool.Wait() }
2 Go发起HTTP请求【技】
服务端:
type Student struct { Name string Age int } func HttpServe() { /** URL:http://localhost:8080 Method:Get */ http.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) { str := r.URL.Query().Get("str") fmt.Println("Get Method Str is " + str) w.Write([]byte("Hello Http Get!")) }) /** URL:http://localhost:8080 Method:Get Param:str */ http.HandleFunc("/get/form", func(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") age := r.URL.Query().Get("age") ageStr, err := strconv.Atoi(age) if err != nil { fmt.Println("err...") } stu := Student{Name: name, Age: ageStr} fmt.Println("Get Method Str is ", stu) w.Write([]byte("Hello Http Get Form!")) }) /** URL:http://localhost:8080 Method:Get Param:str */ http.HandleFunc("/get/json", func(w http.ResponseWriter, r *http.Request) { fmt.Println("req method : ", r.Method) body, err := io.ReadAll(r.Body) if err != nil { fmt.Printf("获取请求体错误 , %v\n", err) return } fmt.Println("请求体 :", string(body)) var stu Student if err = json.Unmarshal(body, &stu); err != nil { fmt.Printf("反序列化失败 , %v\n", err) return } fmt.Printf("反序列化成功,JSON解析结果 %+v", stu) w.Write([]byte("Hello Http Get Form!")) }) err := http.ListenAndServe(":8080", nil) if err != nil { fmt.Println(err) } }
Go发送Http请求:
func HttpGet() { resp, err := http.Get("http://localhost:8080/get?str=ymx") // url if err != nil { fmt.Printf("get请求失败 error: %+v", err) return } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { fmt.Printf("读取Body失败 error: %+v", err) return } fmt.Println(string(body)) } func HttpPost() { resp, err := http.PostForm("http://localhost:8080/form", url.Values{ "name": {"jack"}, }) if err != nil { fmt.Printf("postForm请求失败 error: %+v", err) return } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { fmt.Printf("读取Body失败 error: %+v", err) return } fmt.Println(string(body)) }
3 包循环依赖导致的异常【易】
循环依赖是一个在代码层面很常见的概念了,简单来说就是A依赖B,B依赖A,从而导致的先有蛋还是先有鸡的问题,下面来一个示例:
package_a代码:
package package_a import ( "encoding/json" "other/article5/pack/package_b" ) func MakeStudent(stu package_b.Student) string { bytes, _ := json.Marshal(stu) return string(bytes) }
package_b代码:
package package_b import "other/article5/pack/package_a" type Student struct { Id int64 Name string } func (stu *Student) GetStuJSON() string { return package_a.MakeStudent(*stu) }
测试方法:
package main import ( "fmt" "other/article5/pack/package_b" ) func main() { student := package_b.Student{ Name: "zs", } str:= student.GetStuJSON() fmt.Println(str) }
执行结果:
如何避免循环依赖呢?
说实话没有什么特别好的办法,就是在平时写代码前先做好设计,设计好每一层的依赖关系,尽量不要产生额外的循环依赖即可。