本期看点(技巧类用【技】表示,易错点用【易】表示):

(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)
}

执行结果:

如何避免循环依赖呢?

说实话没有什么特别好的办法,就是在平时写代码前先做好设计,设计好每一层的依赖关系,尽量不要产生额外的循环依赖即可。

转自:https://juejin.cn/post/7180697601220542524

最后编辑: kuteng  文档更新时间: 2024-03-27 11:19   作者:kuteng