1、gin框架的路由是采用洋葱模型,中间件和具体接口采用同样的方式,提前放入handler,只是中间件方法中,有Next()方法,继续执行下一个,具体接口中则没有Next()方法,当然,中间件中也可以采用Abort()方法,终止程序继续往下执行。
详细可以参考这篇文章:
https://blog.csdn.net/raoxiaoya/article/details/109444890
1、同一个类型的struct,且没有不可比较的类型(map,slice等),则可以进行比较。
2、一旦struct中包含不可比较的类型,或者是两个结构体,则不可进行比较,即便两个struct有相同的类型也不可以,因为地址不一样。
1、select和channel配合使用,用于等待一个或多个channel的输出。
1、”_”表示,导入的包仅适用里面的init函数,并不适用里面其他的函数。
2、”.”表示导入的包,在使用里面的函数时,可以省略包名。
1、数组在传参的时候,是值传递,会对整个数组进行拷贝,会消耗大量的内存。
2、slice传参是传递引用,不会消耗大量内存,但同时这又是一个风险点,会改变原值。
1、slice是基于数组实现的,它的底层是数组,可以理解为对底层数组的抽象,因为基于数组实现,所以它的底层的内存分配是连续的,效率非常高,还可以通过索引获取数据,可以迭代及垃圾回收优化。
切片本身并不是动态数组或者数组指针。它内部实现的数据结构通过指针引用底层数组,设定相关属性将数据读写操作限定在指定的区域内。切片本身是一个只读对象,其工作机制类似数组指针的一种封装。
切片对象非常小,是因为它是只有三个字段的数据结构:
指向底层的数组指针
切片的长度
切片的容量
这三个字段就是golang操作底层数组的元数据

首先判断,如果新申请容量大于2倍的旧容量,最终容量就是新申请的容量
否则判断,如果旧切片的长度小于1024,则最终容量就是旧容量的两倍
否则判断,如果旧切片长度大于等于1024,则最终容量从旧容量开始循环增加原来的 1/4, 直到最终容量大于等于新申请的容量
如果最终容量计算值溢出,则最终容量就是新申请容量
伪代码如下:
if cap > 2*oldCap {
newCap = cap
} else if cap < 1024 {
newCap = 2*oldCap
} else {
newCap = oldCap
while (newCap < cap && !overflow) {
newcap += newcap/4
}
if overflow {
newCap = cap
}
}
分两种情况。
情况一:
情况二:
原来数组的容量已经达到了最大值,再想扩容, Go 默认会先开一片内存区域,把原来的值拷贝过来,然后再执行 append() 操作。这种情况丝毫不影响原数组。
要复制一个Slice,最好使用Copy函数。
Go语言中所有的传参都是值传递(传值),都是一个副本,一个拷贝。因为拷贝的内容有时候是非引用类型(int、string、struct等这些),这样就在函数中就无法修改原内容数据;有的是引用类型(指针、map、slice、chan等这些),这样就可以修改原内容数据。
Golang的引用类型包括 slice、map 和 channel。它们有复杂的内部结构,除了申请内存外,还需要初始化相关属性。内置函数 new 计算类型大小,为其分配零值内存,返回指针。而 make 会被编译器翻译成具体的创建函数,由其分配内存和初始化成员结构,返回对象而非指针。(这也是new和make的区别)
Golang中map的底层实现是一个散列表,因此实现map的过程实际上就是实现散表的过程。在这个散列表中,主要出现的结构体有两个,一个叫hmap(a header for a go map),一个叫bmap(a bucket for a Go map,通常叫其bucket)。hmap如下所示:

图中有很多字段,但是便于理解map的架构,你只需要关心的只有一个,就是标红的字段:buckets数组。Golang的map中用于存储的结构是bucket数组。而bucket(即bmap)的结构是怎样的呢?
bucket:

相比于hmap,bucket的结构显得简单一些,标红的字段依然是“核心”,我们使用的map中的key和value就存储在这里。“高位哈希值”数组记录的是当前bucket中key相关的“索引”,稍后会详细叙述。还有一个字段是一个指向扩容后的bucket的指针,使得bucket会形成一个链表结构。
整体的结构应该是这样的:

Golang把求得的哈希值按照用途一分为二:高位和低位。低位用于寻找当前key属于hmap中的哪个bucket,而高位用于寻找bucket中的哪个key。
需要特别指出的一点是:map中的key/value值都是存到同一个数组中的。这样做的好处是:在key和value的长度不同的时候,可以消除padding带来的空间浪费。

Map的扩容: 当Go的map长度增长到大于加载因子所需的map长度时,Go语言就会将产生一个新的bucket数组,然后把旧的bucket数组移到一个属性字段oldbucket中。 注意:并不是立刻把旧的数组中的元素转义到新的bucket当中,而是,只有当访问到具体的某个bucket的时候,会把bucket中的数据转移到新的bucket中。
实体类型以值接收者实现接口的时候,不管是实体类型的值,还是实体类型值的指针,都实现了该接口。
实体类型以指针接收者实现接口的时候,只有指向这个类型的指针才被认为实现了该接口
非常详细的说明,可以查看:
https://studygolang.com/articles/9624
程序超时
我们起一个本地的 http serice,名字叫"lazy",这个 http server 会随机的发出一些慢请求,要等6秒以上才返回,我们使用这个程序来模拟我们的被调用方 hang 住的情况,代码如下:
package main import ( "net/http" "math/rand" "fmt" "time" ) func lazyHandler(w http.ResponseWriter, req *http.Request) { ranNum := rand.Intn(2) if ranNum == 0 { time.Sleep(6 * time.Second) fmt.Fprintf(w, "slow response, %d\n", ranNum) fmt.Printf("slow response, %d\n", ranNum) return } fmt.Fprintf(w, "quick response, %d\n", ranNum) fmt.Printf("quick response, %d\n", ranNum) return } func main() { http.HandleFunc("/", lazyHandler) http.ListenAndServe(":9200", nil) }然后我们写一个主动调用的 http service,他会调用我们刚才写的"lazy",我们使用 context,来解决超过2秒的慢请求问题,如下代码:
package main import ( "context" "net/http" "fmt" "sync" "time" "io/ioutil" ) var ( wg sync.WaitGroup ) type ResPack struct { r *http.Response err error } func work(ctx context.Context) { tr := &http.Transport{} client := &http.Client{Transport: tr} defer wg.Done() c := make(chan ResPack, 1) req, _ := http.NewRequest("GET", "http://localhost:9200", nil) go func() { resp, err := client.Do(req) pack := ResPack{r: resp, err: err} c <- pack }() select { case <-ctx.Done(): tr.CancelRequest(req) <-c fmt.Println("Timeout!") case res:= <-c: if res.err != nil { fmt.Println(res.err) return } defer res.r.Body.Close() out, _ := ioutil.ReadAll(res.r.Body) fmt.Printf("Server Response: %s", out) } return } func main() { ctx, cancel := context.WithTimeout(context.Background(), 2 * time.Second) defer cancel() wg.Add(1) go work(ctx) wg.Wait() fmt.Println("Finished") } 在 main 函数中,我们定义了一个超时时间为2秒的 context,传给真正做事的work(),work接收到这个 ctx 的时候,需要等待 ctx.Done() 返回,因为 channel 关闭的时候,ctx.Done() 会受到空值,当 ctx.Done()返回时,就意味着 context 已经超时结束,要做一些扫尾工作然后 return 即可。生成Request ID中间件
在 Golang1.7 中,
"net/http"原生支持将Context嵌入到*http.Request中,并且提供了http.Request.Conext()和http.Request.WithContext()这两个函数来新建一个 context 和 将 context 加入到一个http.Request实例中。下面的程序演示了一下利用WithValue()创建一个可以储存 K-V 的 context,然后写一个中间件来自动获取 http头部的 "X-Rquest-ID"值,加入到 context 中,使业务函数可以直接取到该值,代码如下:package main import ( "net/http" "context" "fmt" ) const requestIDKey = "rid" func newContextWithRequestID(ctx context.Context, req *http.Request) context.Context { reqID := req.Header.Get("X-Request-ID") if reqID == "" { reqID = "0" } return context.WithValue(ctx, requestIDKey, reqID) } func requestIDFromContext(ctx context.Context) string { return ctx.Value(requestIDKey).(string) } func middleWare(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ctx := newContextWithRequestID(req.Context(), req) next.ServeHTTP(w, req.WithContext(ctx)) }) } func h(w http.ResponseWriter, req *http.Request) { reqID := requestIDFromContext(req.Context()) fmt.Fprintln(w, "Request ID: ", reqID) return } func main() { http.Handle("/", middleWare(http.HandlerFunc(h))) http.ListenAndServe(":9201", nil) }
1、goroutine是golang并行设计的核心。
2、goroutine说到底就是协程,但是它比线程更小,几十个协程可能底层对应的就是五六个线程,golang内部帮你实现了这些协程之间的内存共享。
3、执行协程,只需要极少的栈内存,大概是4~5kb,也正因为这样,在一台机器上可以开启成千上万个协程,只要内存足够即可。
4、golang既支持通过共享内存进行通讯,也支持通过消息传递的方式进行通讯,后者是CSP的一个变种,效率更高,也是golang推荐的方式。
总结为:用户空间 避免了内核态和用户态的切换导致的成本、可以由语言和框架层进行调度、更小的栈空间允许创建大量的实例。
关于golang的CSP模型说明,可以查看这篇文章:
https://www.jianshu.com/p/36e246c6153d
1、以下程序输出什么
package main
import (
"fmt"
)
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
a := 1
b := 2
defer calc("1", a, calc("10", a, b))
a = 0
defer calc("2", a, calc("20", a, b))
b = 1
//time.Sleep(1000 * time.Second)
}
output:
10 1 2 3
20 0 2 2
2 0 2 2
1 1 3 4
package main
import (
"fmt"
)
func main() {
s := make([]int, 5)
s = append(s, 1, 2, 3)
fmt.Println(s)
}
output:
[0 0 0 0 0 1 2 3]
package main
import (
"fmt"
)
type People interface {
Speak(string) string
}
type Stduent struct{}
func (stu *Stduent) Speak(think string) (talk string) {
if think == "bitch" {
talk = "You are a good boy"
} else {
talk = "hi"
}
return
}
func main() {
var peo People = Stduent{}
think := "bitch"
fmt.Println(peo.Speak(think))
}
//编译不过去,需要修改一行代码:
var peo People = Stduent{} 修改为 var peo People = &Stduent{}
package main
import (
"fmt"
)
type People interface {
Show()
}
type Student struct{}
func (stu *Student) Show() {
}
func live() People {
var stu *Student
return stu
}
func main() {
if live() == nil {
fmt.Println("AAAAAAA")
} else {
fmt.Println("BBBBBBB")
}
}
output:
BBBBBBB
原因:指针类型的地址和nil不一致,不相等
package main
import (
"fmt"
)
func main() {
defer_call()
}
func defer_call() {
defer func() { fmt.Println("打印前") }()
defer func() { fmt.Println("打印中") }()
defer func() { fmt.Println("打印后") }()
panic("触发异常")
}
output:
打印后
打印中
打印前
panic: 触发异常
原因:遇到panic,遍历本协程的defer链表,并按照先进后出的顺序执行
