本期看点(技巧类用【技】表示,易错点用【易】表示):
(1)goroutine控制并发数量的方式【技】
(2)Go发起HTTP请求【技】
(3)包循环依赖导致的异常【易】
正文如下:
首先我们思考一个问题,为什么要控制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()
}
复制代码
服务端:
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))
}
复制代码
循环依赖是一个在代码层面很常见的概念了,简单来说就是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)
}
复制代码
执行结果:
如何避免循环依赖呢?
说实话没有什么特别好的办法,就是在平时写代码前先做好设计,设计好每一层的依赖关系,尽量不要产生额外的循环依赖即可。