Go语言知识分享(二):编程思想

接口

接口的概念

  • 接口由使用者定义的
  • 接口的实现是隐式的,只需要实现接口中的方法
  • 实现同一种方法的结构体可以关联起来
package main

import (
	"fmt"
	"goprogramming/infra"
)

func getRetriever() retriever {
	return infra.Retriever{}
}

//something that can "Get"
// 接口:实现了get方法
type retriever interface {
	Get(string) string
}

func main() {
	var r retriever = getRetriever()
	fmt.Println(r.Get("https://www.imooc.com"))
}

package main

import (
	"fmt"
	"goprogramming/retriever/mock"
	real2 "goprogramming/retriever/real"
)

type Retriever interface {
	Get(url string) string
}

func download(r Retriever) string {
	return r.Get("https://www.imooc.com")
}

func main() {
	var r Retriever
	r = mock.Retriever{"this is a imooc.com"}
	r = real2.Rretriever{}
	fmt.Println(download(r))
}

鸭子类型—类似python的多态,但是不是一个意思

  • 描述事物的外部行为而非内部结构

  • 严格来说go属于结构化类型系统,类似duck typing

接口变量里面有什么:

  • 接口变量自带指针
  • 接口变量同样采用值传递,几乎不需要使用接口的指针,肚子里面有指针
  • 指针接收者只能以指针的方式使用,值接收者都可以
  • 表示任何类型interface{}:type queue []interface{} v.(int)强制类型转换
package main

import (
	"fmt"
	"goprogramming/retriever/mock"
	real2 "goprogramming/retriever/real"
	"time"
)

type Retriever interface {
	Get(url string) string
}

func download(r Retriever) string {
	return r.Get("https://www.imooc.com")
}

func main() {
	var r Retriever
	r = mock.Retriever{"this is a imooc.com"}
	insepct(r)
	r = &real2.Rretriever{
		UserAgent: "Mozilla/5.0",
		TimeOut:   time.Minute,
	}

	insepct(r)

	//Type assertion
	realRetriever := r.(*real2.Rretriever) //取得内部元素
	fmt.Println(realRetriever.TimeOut)
	if mockRetriever, ok := r.(mock.Retriever); ok {
		fmt.Println(mockRetriever.Contents)
	} else {
		fmt.Println("not a mock retriever")
	}

	//fmt.Println(download(r))
}

func insepct(r Retriever) {
	fmt.Printf("%T %v\n", r, r)
	switch v := r.(type) {
	case mock.Retriever:
		fmt.Println("contents:", v.Contents)
	case *real2.Rretriever:
		fmt.Println("UserAgent", v.UserAgent)

	}

}

package real

import (
	"net/http"
	"net/http/httputil"
	"time"
)

type Rretriever struct {
	UserAgent string
	TimeOut   time.Duration
}

func (r *Rretriever) Get(url string) string {
	//TODO implement me
	resp, err := http.Get(url)
	if err != nil {
		panic(err)
	}
	result, err := httputil.DumpResponse(resp, true)
	resp.Body.Close()
	if err != nil {
		panic(err)
	}
	return string(result)
}

接口的组合/ 通过接口协议完成go的排序

// 实现者
package mock

type Retriever struct {
	Contents string
}

func (r *Retriever) Post(usr string, form map[string]string) string {
	//TODO implement me
	r.Contents = form["contents"]
	return "ok"
}

func (r *Retriever) Get(url string) string {
	return r.Contents
}
// 使用者
const url = "https://www.imooc.com"

type Retriever interface {
	Get(url string) string
}

type Poster interface {
	Post(usr string, form map[string]string) string
}

type RetrieverPoster interface {
	Retriever
	Poster
}


func session(s RetrieverPoster) string {
	s.Post(url, map[string]string{
		"contents": "another faked imooc.com",
	})
	return s.Get(url)
}

func main() {
	
	fmt.Println("try a session")
	s := mock.Retriever{"this is a fake imooc.com"}
	fmt.Println(session(&s))
}
package main

import (
	"fmt"
	"sort"
)

type Course struct {
	Name  string
	Price int
	Url   string
}

type Courses []Course

func (c Courses) Len() int {
	return len(c)
}

func (c Courses) Less(i, j int) bool {
	return c[i].Price < c[j].Price
}
func (c Courses) Swap(i, j int) {
	c[i], c[j] = c[j], c[i]
}

func main() {
	// 通过sort排序
	// 冒泡排序,快速排序,归并排序,插入排序,桶排序 算法本质是一样的
	// 排序算法是否能够应对各种类型的排序

	couses := Courses{
		Course{Name: "django", Price: 300, Url: ""},
		Course{Name: "scrapy", Price: 100, Url: ""},
		Course{Name: "go", Price: 400, Url: ""},
		Course{Name: "tornado", Price: 200, Url: ""},
	}
	sort.Sort(couses)// 协议,你的目的不是要告诉别人具体的类型,重要的是你的类型必须提供具体的方法

	for _, v := range couses {
		fmt.Println(v)
	}

}

常用系统接口

// 重写string方法的接口
type Retriever struct {
	Contents string
}

func (r *Retriever) String() string {
	//TODO implement me
	return fmt.Sprintf("Retriever: {Contents=%s}", r.Contents)
}

// Reader、Writer

函数式编程

函数与闭包

函数式编程vs函数指针

  • 函数是一等公民:参数,变量,返回值都可以是函数,python中一样
  • 高阶函数:
  • 函数 -> 闭包,装饰器

正统的函数式编程

  • 不可变性:不能有状态(变量),只有常量和函数
  • 函数只能有一个参数
  • go语言不作严格限定

闭包:

函数体中包含局部变量,自由变量;扩充了函数中的局部变量种类和范围

package main

import "fmt"

func adder() func(int) int {
	sum := 0
	return func(v int) int {
		sum += v
		return sum
	}
}

func main() {
	a := adder()
	for i := 0; i < 10; i++ {
		fmt.Printf("0 + 1 + ...+ %d = %d \n", i, a(i))
	}

}

// 正统的函数式编程的写法, 采用了递归的方法
type iAdder func(int) (int, iAdder)

func adder2(base int) iAdder {
	return func(v int) (int, iAdder) {
		return base + v, adder2(base + v)
	}
}

func main() {
	a := adder2(0)
	for i := 0; i < 10; i++ {
		var s int
		s, a = a(i)
		fmt.Printf("0 + 1 + ...+ %d = %d \n", i, s)
	}
}
// 例一:实现斐波那契

package main

import "fmt"

// 1, 1, 2, 3, 5, 8, 13, ...
// 通过闭包不断的改变自由变量的值
func fibonacci() func() int {
	a, b := 0, 1
	return func() int {
		a, b = b, a+b
		fmt.Println(a)
		return a
	}
}

func main() {
	f := fibonacci()

	f() // 1
	f() // 1
	f() // 2
	f() // 3
	f() // 5
	f() // 8
	f() // 13
	f() // 21
}
// 给函数定义接口,要定义具体的类型,因为go是强类型语言

package main

import (
	"bufio"
	"fmt"
	"io"
	"strings"
)

// 1, 1, 2, 3, 5, 8, 13, ...
// 通过闭包不断的改变自由变量的值
func fibonacci() intGen {
	a, b := 0, 1
	return func() int {
		a, b = b, a+b
		fmt.Println(a)
		return a
	}
}


type intGen func() int

func (g intGen) Read(p []byte) (n int, err error) {
	//TODO implement me
	next := g()
	if next > 10000 {
		return 0, io.EOF
	}
	s := fmt.Sprintf("%d\n", next)
	
	// if p is too small
	return strings.NewReader(s).Read(p)
}

func printFileContents(reader io.Reader) {
	scanner := bufio.NewScanner(reader)
	for scanner.Scan() {
		fmt.Println(scanner.Text())
	}
}

func main() {
	f := fibonacci()

	printFileContents(f)
}

// 函数作为参数传入
package tree

import "fmt"

func (node *Node) Traverse() {
	node.TraverseFunc(func(node *Node) {
		node.Print()
	})
	fmt.Println()
}

func (node *Node) TraverseFunc(f func(node *Node)){
	if node == nil {
		return
	}
	node.Left.TraverseFunc(f)
	f(node)
	node.Right.TraverseFunc(f)
}

错误处理和资源管理

确保在函数结束时发生

参数在defer语句时计算,进入一个栈中,先进后出

defer列表为先进后出

open/close使用

LOCK/unlock

func tryDefer() {
	for i := 0; i < 100; i++ {
		defer fmt.Println(i)
		if i == 30 {
			panic("printed to many")
		}
	}
}

func writeFile(filename string) {
	file, err := os.Create(filename) //创建写文件
	if err != nil {
		panic(err)
	}
	defer file.Close()

	write := bufio.NewWriter(file) //先写到内存,一定程度后存到文件中
	defer write.Flush()
	f := fib.Fibonacci()
	for i := 0; i < 20; i++ {
		fmt.Fprintln(write, f())
	}

}

错误处理

func writeFile(filename string) {
	file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)

	err = errors.New("this is a custom error") //自己定义error
	if err != nil {
		//fmt.Println("Error:", err.Error())
		if pathError, ok := err.(*os.PathError); !ok {
			panic(err)
		} else {
			fmt.Println(pathError.Op, pathError.Path, pathError.Err)
		}
		return
	}
	defer file.Close()

	write := bufio.NewWriter(file) //先写到内存,一定程度后存到文件中
	defer write.Flush()
	f := fib.Fibonacci()
	for i := 0; i < 20; i++ {
		fmt.Fprintln(write, f())
	}

}

错误处理逻辑

函数作为参数和返回值,包装函数

package main

import (
	"goprogramming/errhanding/filelistingserver/filelisting"
	"log"
	"net/http"
	"os"
)

type appHandler func(writer http.ResponseWriter, request *http.Request) error

// 单独进行错误处理
func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
	return func(writer http.ResponseWriter, request *http.Request) {
		err := handler(writer, request)
		if err != nil {
			log.Printf("Error handling request: %s", err.Error())
			code := http.StatusOK
			switch {
			case os.IsNotExist(err):
				code = http.StatusNotFound
			case os.IsPermission(err):
				code = http.StatusForbidden
			default:
				code = http.StatusInternalServerError
			}

			http.Error(writer, http.StatusText(code), code)

		}
	}
}
func main() {
	http.HandleFunc("/list/", errWrapper(filelisting.HandleFileList))
	err := http.ListenAndServe(":8888", nil)
	if err != nil {
		panic(err)
	}
}
package filelisting

import (
	"io/ioutil"
	"net/http"
	"os"
)

// 统一处理接口,遇到错误抛出来
func HandleFileList(writer http.ResponseWriter, request *http.Request) error {
	path := request.URL.Path[len("/list/"):]
	file, err := os.Open(path)
	if err != nil {
		return err
	}
	defer file.Close()
	all, err := ioutil.ReadAll(file)

	if err != nil {
		return err
	}

	writer.Write(all)
	return nil
}

error vs panic

panic尽量不要用

意料之中的使用error,panic尽量不要使用

意料之外的:使用panic。如:数组越界

type assertion

package filelisting

import (
	"io/ioutil"
	"net/http"
	"os"
	"strings"
)

const prefix = "/list/" +
	""

type userError string

func (e userError) Error() string {
	return e.Message()
}

func (e userError) Message() string {
	return string(e)
}

func HandleFileList(writer http.ResponseWriter, request *http.Request) error {
	if strings.Index(request.URL.Path, prefix) != 0 {
		return userError("path must start" + "with" + prefix)
	}

	path := request.URL.Path[len(prefix):]
	file, err := os.Open(path)
	if err != nil {
		return err
	}
	defer file.Close()
	all, err := ioutil.ReadAll(file)

	if err != nil {
		return err
	}

	writer.Write(all)
	return nil
}

package main

import (
	"goprogramming/errhanding/filelistingserver/filelisting"
	"log"
	"net/http"
	"os"
)

type appHandler func(writer http.ResponseWriter, request *http.Request) error
type userError interface {
	error
	Message() string
}

func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
	return func(writer http.ResponseWriter, request *http.Request) {
		defer func() {

			if r := recover(); r != nil { //保护web应用在遇到错的时候可以重启
				log.Printf("panic:%s", r)
				http.Error(writer,
					http.StatusText(http.StatusInternalServerError),
					http.StatusInternalServerError)
			}
		}()
		err := handler(writer, request)
		if err != nil {
			log.Printf("Error handling request: %s", err.Error())
			if userError, ok := err.(userError); ok {
				http.Error(writer, userError.Message(), http.StatusBadRequest)
				return
			}
			code := http.StatusOK
			switch {
			case os.IsNotExist(err):
				code = http.StatusNotFound
			case os.IsPermission(err):
				code = http.StatusForbidden
			default:
				code = http.StatusInternalServerError
			}

			http.Error(writer, http.StatusText(code), code)

		}
	}
}

func main() {
	http.HandleFunc("/", errWrapper(filelisting.HandleFileList))
	err := http.ListenAndServe(":8888", nil)
	if err != nil {
		panic(err)
	}
}

测试

传统测试vs表格驱动测试

传统测试:

测试数据和测试逻辑混在一起

出错信息不明确

一旦一个数据出错测试全部结束

表格驱动测试:

分离的测试数据和测试逻辑

明确的出错信息

可以部分失败

go语言的语法可以更容易的实现驱动测试

package main

import "testing"

func TestTrianle(t *testing.T) {
	tests := []struct{ a, b, c int }{
		{3, 4, 5},
		{5, 12, 13},
		{8, 15, 17},
		{12, 35, 37},
		{3000, 4000, 5000},
	}

	for _, tt := range tests {
		if actual := calcTriangel(tt.a, tt.b); actual != tt.c {
			t.Errorf("calctriangle(%d, %d)"+"got %d, expected %d",
				tt.a, tt.b, actual, tt.c)
		}
	}
}

func triangle() {
	var a, b int = 3, 4

	fmt.Println(calcTriangel(a, b))
}

func calcTriangel(a, b int) int {
	var c int
	c = int(math.Sqrt(float64(a*a + b*b)))
	return c
}
$ go test . #在目录下

性能测试

func BenchmarkSubstr(b *testing.B) {
	s := "黑化肥挥发发灰会花飞灰化肥挥发发黑会飞花"
	for i := 0; i < 13; i++ {
		s += s
	}
	ans := 8
	b.Logf("len(s)= %d", len(s))
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		actual := lenthOfNoneRepeatingSubStr(s)
		if actual != ans {
			b.Errorf("Got %d for input %s; "+"exepected %d", actual, s, ans)
		}
	}
}

$ go test -bench .
$ t #生成一个cpu.out 性能日志
$ go tool pprof cpu.out  #查看日志
		$web # 查看cpu运行中主要阻塞点

性能优化

-cpuprofile :go test -bench . -cpuprofile cpu.out #生成一个cpu.out 性能日志

查看性能数据:go tool pprof cpu.out # web查看日志

分析主要问题点

优化代码

再去获得性能日志,查看数据,优化

package nonrepeating

var lastOccured = make([]int, 0xffff) //n空间换时间
func lenthOfNoneRepeatingSubStr(s string) int {
	for i := range lastOccured {
		lastOccured[i] = -1
	}
	start := 0
	maxLength := 0

	for i, ch := range []rune(s) {

		if lastI := lastOccured[ch]; lastI != -1 && lastI >= start {
			start = lastI + 1
		}
		if i-start+1 > maxLength {
			maxLength = i - start + 1
		}
		lastOccured[ch] = i
	}
	return maxLength
}

http 测试

package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
	"strings"
	"testing"
)

var tests = []struct {
	h       appHandler
	code    int
	message string
}{
	{errPanic, 500, ""},
	{errUserError, 400, "user error"},
	{errNotFound, 404, "Not Found"},
	{errNotPermission, 403, "Forbidden"},
	{errUnknown, 500, "Internal Server Error"},
	{noError, 200, "no error"},
}

func errPanic(writer http.ResponseWriter, request *http.Request) error {
	panic(123)
}

type testingUserError string

func (e testingUserError) Error() string {
	return e.Message()
}

func (e testingUserError) Message() string {
	return string(e)
}

func errUserError(writer http.ResponseWriter, request *http.Request) error {
	return testingUserError("user error")
}

func errNotFound(writer http.ResponseWriter, request *http.Request) error {
	return os.ErrNotExist
}
func errNotPermission(writer http.ResponseWriter, request *http.Request) error {
	return os.ErrPermission
}
func errUnknown(writer http.ResponseWriter, request *http.Request) error {
	return errors.New("unknown error")
}
func noError(writer http.ResponseWriter, request *http.Request) error {
	fmt.Fprintln(writer, "no error")
	return nil
}

func TestErrWrapper(t *testing.T) {

	for _, tt := range tests {
		f := errWrapper(tt.h)
		response := httptest.NewRecorder() // 假的request和response
		request := httptest.NewRequest(
			http.MethodGet, "http://www.imooc.com", nil)
		f(response, request) //调用入口
		verifyResponse(t, response.Result(), tt.code, tt.message)

	}
}

// 这是测试整个服务器的,结果虽然是一样的
func TestErrWrapperInserver(t *testing.T) {
	for _, tt := range tests {
		f := errWrapper(tt.h)
		server := httptest.NewServer(http.HandlerFunc(f)) //起服务器
		resp, _ := http.Get(server.URL)
		verifyResponse(t, resp, tt.code, tt.message)

	}
}

func verifyResponse(t *testing.T, resp *http.Response,
	exepectedcode int,
	expectedmessage string) {
	b, _ := ioutil.ReadAll(resp.Body)
	body := strings.Trim(string(b), "\n")
	if resp.StatusCode != exepectedcode || body != expectedmessage {
		t.Errorf("expect (%d %s);"+"got (%d %s)", exepectedcode, expectedmessage,
			resp.StatusCode, body)
	}
}

//需要测试的代码
package main

import (
	"goprogramming/errhanding/filelistingserver/filelisting"
	"log"
	"net/http"
	"os"
)

type appHandler func(writer http.ResponseWriter, request *http.Request) error
type userError interface {
	Error() string
	Message() string
}

func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
	return func(writer http.ResponseWriter, request *http.Request) {
		defer func() {

			if r := recover(); r != nil { //保护web应用在遇到错的时候可以重启
				log.Printf("panic:%s", r)
				http.Error(writer,
					http.StatusText(http.StatusInternalServerError),
					http.StatusInternalServerError)
			}
		}()
		err := handler(writer, request)
		if err != nil {
			log.Printf("Error handling request: %s", err.Error())
			if userError, ok := err.(userError); ok {
				http.Error(writer, userError.Message(), http.StatusBadRequest)
				return
			}
			code := http.StatusOK
			switch {
			case os.IsNotExist(err):
				code = http.StatusNotFound
			case os.IsPermission(err):
				code = http.StatusForbidden
			default:
				code = http.StatusInternalServerError
			}

			http.Error(writer, http.StatusText(code), code)

		}
	}
}

func main() {
	http.HandleFunc("/", errWrapper(filelisting.HandleFileList))
	err := http.ListenAndServe(":8888", nil)
	if err != nil {
		panic(err)
	}
}

package filelisting

import (
	"io/ioutil"
	"net/http"
	"os"
	"strings"
)

const prefix = "/list/" +
	""

type userError string

func (e userError) Error() string {
	return e.Message()
}

func (e userError) Message() string {
	return string(e)
}

func HandleFileList(writer http.ResponseWriter, request *http.Request) error {
	if strings.Index(request.URL.Path, prefix) != 0 {
		return userError("path must start" + "with" + prefix)
	}

	path := request.URL.Path[len(prefix):]
	file, err := os.Open(path)
	if err != nil {
		return err
	}
	defer file.Close()
	all, err := ioutil.ReadAll(file)

	if err != nil {
		return err
	}

	writer.Write(all)
	return nil
}

生成文档和式例

  • 用注释写文档
  • 在测试中加入example,test文件
  • 用go doc / godoc来查看生成文档
$ go doc 结构体
$ godoc -http 6060

Grountine 协程

goroutine(coroutine)

  • 轻量级的“线程”
  • 非抢占式多任务处理,由协程主动交出控制权:由协程内部主动处理,不需要存上下文,只需要处理切换的点,对资源的需求少
  • 编译器/解释器/虚拟机层面的多任务;go中是编译器级别的多任务
  • 多个协程可以在一个或者多个线程中运行,由调度器控制的
package main

import (
	"fmt"
	"time"
)

func main() {
	for i := 0; i < 1000; i++ {
		go func(i int) {
			for {
				fmt.Printf("hello from"+"goroutine %d\n", i)
			}
		}(i)
	}
	time.Sleep(time.Millisecond)
}

package main

import (
	"fmt"
	"runtime"
	"time"
)

func main() {
	var a [10]int
	for i := 0; i < 10; i++ {
		go func(i int) {
			for {
				a[i]++
				runtime.Gosched() //主动交出控制权,让别人有机会运行
			}
		}(i)
	}
	time.Sleep(time.Millisecond)
	fmt.Println(a)
}
$ go run -race goroutine.go 

sub-corutine

  • 子程序是协程的特例
  • 普通函数:线程中进行dowork,然后做完之后交还给main函数
  • 协程序:main和dowork进行双向流通,两个人之间可以互相沟通,运行在一个线程或多个线程中

其他语言的协程

  • go语言原生支持协程

  • java不支持、python使用yield关键字实现、python3.5之后使用asyncio对协程原生支持

  • go语言通过调度器,将多个或一个协程放在线程中

  • 任何函数只需要加上go就能送给调度器运行

  • 不需要在定义时区分是否是异步函数,相对于python在定义时说明自己是协程,编码有困难

  • 调度器在合适的点切换:相比较而言不需要将传统意义上的协程显示的写出来

  • 在goroutine中,由调度器控制切换

  • 使用-race来检测数据访问冲突

goroutine可能切换的点

  • Io/select
  • channel
  • 等待锁
  • 函数调用(有时)
  • runtime.gosched()手动提供切换点
  • 只是参考,不能保证切换,不能保证在其他地方不切换,最大可以开的线程数是物理机的核数

channel

goroutine和goroutine之间的双向通道是channel

goroutine和goroutine之间的调度是由go本身的调度器实现的

package main

import (
	"fmt"
	"time"
)

func chanDemo() {
	//var c chan int //n c == nil 定义变量但是没有创建channel
	c := make(chan int) //做一个channel出来
	go func() {
		for {
			n := <-c
			fmt.Println(n)
		}
	}()
	c <- 1
	c <- 2
	time.Sleep(time.Millisecond)
}

func main() {
	chanDemo()
}

channel是一等公民

// channel作为参数
func worker(id int, c chan int) {
	for {

		fmt.Printf("worker %d received %c \n", id, <-c)
	}
}

func chanDemo() {
	var channels [10]chan int
	for i := 0; i < 10; i++ {
		channels[i] = make(chan int) //做一个channel出来
		go worker(i, channels[i])
	}

	for i := 0; i < 10; i++ {
		channels[i] <- 'a' + i
	}
	for i := 0; i < 10; i++ {
		channels[i] <- 'A' + i
	}
	time.Sleep(time.Millisecond)
}

func main() {
	chanDemo()
}
// channel作为返回值
func createWorker(id int) chan<- int { // chan是一个收数据的, 给chan方向:<-chan(发数据)
	c := make(chan int)
	go func() {
		for {

			fmt.Printf("worker %d received %c \n", id, <-c)
		}
	}()
	return c
}

func chanDemo() {
	var channels [10]chan<- int // chan只是收数据
	for i := 0; i < 10; i++ {
		channels[i] = make(chan int) //做一个channel出来
		channels[i] = createWorker(i)
	}

	for i := 0; i < 10; i++ {
		channels[i] <- 'a' + i
	}
	for i := 0; i < 10; i++ {
		channels[i] <- 'A' + i
	}
	time.Sleep(time.Millisecond)
}

func main() {
	chanDemo()
}

Buffered channel: 为channel设置缓冲区

func bufferedChannel() {
	c := make(chan int, 3) //设置3个缓冲,超过缓冲就会形成死锁
	go worker(0, c)
	c <- 'a'
	c <- 'b'
	c <- 'c'
	c <- 'd'
	time.Sleep(time.Millisecond)
}

func main() {
	//chanDemo()
	bufferedChannel()
}
// channelclose, 采用range来收
func worker(id int, c chan int) {
	for {
		n, ok := <-c
		if !ok {
			break
		}
		fmt.Printf("worker %d received %c \n", id, n)

	}
}

func worker(id int, c chan int) {
	for n := range c {
		fmt.Printf("workder %d recieved %d \n", id, n)
	}
}

func channelClose() {
	c := make(chan int)
	go worker(0, c)
	c <- 'a'
	c <- 'b'
	c <- 'c'
	c <- 'd'
	close(c) //关闭发送
	time.Sleep(time.Millisecond)
}

func main() {
	//chanDemo()
	//bufferedChannel()
	channelClose()
}
不要通过共享内存来通信,通过通信来共享内存:csp模型
// 没有并行
func doWorker(id int, c chan int, done chan bool) {
	for n := range c {
		fmt.Printf("workder %d recieved %c \n", id, n)
		done <- true
	}
}

type worker struct {
	in   chan int
	done chan bool
}

func createWorker(id int) worker { // chan是一个收数据的, 给chan方向:<-chan(发数据)
	w := worker{
		in:   make(chan int),
		done: make(chan bool),
	}
	go doWorker(id, w.in, w.done)
	return w
}

func chanDemo() {
	var workers [10]worker
	for i := 0; i < 10; i++ {
		workers[i] = createWorker(i)
	}

	for i := 0; i < 10; i++ {
		workers[i].in <- 'a' + i
		<-workers[i].done // 没有并行
	}
	for i := 0; i < 10; i++ {
		workers[i].in <- 'A' + i
		<-workers[i].done
	}
}

func main() {
	chanDemo()

}

// 并行的方法
func doWorker(id int, c chan int, done chan bool) {
	for n := range c {
		fmt.Printf("workder %d recieved %c \n", id, n)
		go func() { done <- true }() // 开一个goroutine去确认结束
	}
}

type worker struct {
	in   chan int
	done chan bool
}

func createWorker(id int) worker { // chan是一个收数据的, 给chan方向:<-chan(发数据)
	w := worker{
		in:   make(chan int),
		done: make(chan bool),
	}
	go doWorker(id, w.in, w.done)
	return w
}

func chanDemo() {
	var workers [10]worker
	for i := 0; i < 10; i++ {
		workers[i] = createWorker(i)
	}

	for i, worker := range workers {
		worker.in <- 'a' + i
	}
	for i, worker := range workers {
		worker.in <- 'A' + i
	}

	for _, worker := range workers {
		<-worker.done
		<-worker.done
	}
}

func main() {
	chanDemo()

}

// 使用内置的wg来控制channel处理完成之后的停止操作, 函数式编程
func doWorker(id int, w worker) {
	for n := range w.in {
		fmt.Printf("workder %d recieved %c \n", id, n)
		w.done()
	}
}

type worker struct {
	in chan int
	done func()
}

func createWorker(id int, wg *sync.WaitGroup) worker {
	w := worker{
		in: make(chan int),
		done: func(){
			wg.Done()
	},
	}
	go doWorker(id, w)
	return w
}

func chanDemo() {
	var wg sync.WaitGroup

	var workers [10]worker
	for i := 0; i < 10; i++ {
		workers[i] = createWorker(i, &wg)
	}

	for i, worker := range workers {
		worker.in <- 'a' + i
		wg.Add(1)
	}
	for i, worker := range workers {
		worker.in <- 'A' + i
		wg.Add(1)
	}

	wg.Wait()

}

func main() {
	chanDemo()

}
select
func generator() chan int {
	out := make(chan int)
	go func() {
		i := 0
		for {
			time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
			out <- i
			i++
		}
	}()

	return out
}

func main() {
	var c1, c2 = generator(), generator()
	for {
		select {
		case n := <-c1:
			fmt.Println("received from c1:", n) // 这里会形成阻塞
		case n := <-c2:
			fmt.Println("received from c2:", n)
		}
	}
}
// 非阻塞式
func generator() chan int {
	out := make(chan int)
	go func() {
		i := 0
		for {
			time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
			out <- i
			i++
		}
	}()

	return out
}

func worker(id int, c chan int) {
	for n := range c {
		fmt.Printf("workder %d recieved %d \n", id, n)
	}
}

func createWorker(id int) chan<- int {
	c := make(chan int)
	go worker(id, c)
	return c
}

func main() {
	var c1, c2 = generator(), generator()
	var worker = createWorker(0)

	n := 0
	hasValue := false
	for {
		var activeWorker chan<- int
		if hasValue {
			activeWorker = worker
		}
		select {
		case n = <-c1:
			hasValue = true
		case n = <-c2:
			hasValue = true
		case activeWorker <- n:
			hasValue = false

		}
	}
}

func generator() chan int {
	out := make(chan int)
	go func() {
		i := 0
		for {
			time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
			out <- i
			i++
		}
	}()

	return out
}

func worker(id int, c chan int) {
	for n := range c {
		time.Sleep(time.Second)
		fmt.Printf("workder %d recieved %d \n", id, n)
	}
}

func createWorker(id int) chan<- int {
	c := make(chan int)
	go worker(id, c)
	return c
}

func main() {
	var c1, c2 = generator(), generator()
	var worker = createWorker(0)

	var values []int
	tm := time.After(10 * time.Second) // 每10秒返回一个时间到一个chan中,程序从运行开始就算时间
	tick := time.Tick(time.Second) // 每秒出一个tick
	for {
		var activeWorker chan<- int
		var activateValue int
		if len(values) > 0 {
			activeWorker = worker
			activateValue = values[0]
		}
		select {
		case n := <-c1:
			values = append(values, n)
		case n := <-c2:
			values = append(values, n)
		case activeWorker <- activateValue:
			values = values[1:]
		case <-time.After(800 * time.Millisecond): // 每次运行的时间间隔
			fmt.Println("timeout")
		case <-tick:
			fmt.Println("queue len=", len(values))
		case <-tm:
			fmt.Println("bye")
			return

		}
	}
}

注意:

  • select里面case是一个nil—chan的时候是阻塞的
  • 非阻塞的使用
  • 定时器的使用
  • select的nil channel,放置select里面阻塞,但是非阻塞之后为了防止出现消费和产生的速度不一致,因此要用队列来进行数据收集和消费;故可以用定时器来看队列长度和是否长时间没有产生数据
传统的并发机制

共享内存的形式

waitgroup:

Mutex: 锁同步机制

con:条件锁

type atomicInt struct {
	value int
	lock  sync.Mutex
}

func (a *atomicInt) increment() {
	a.lock.Lock()
	a.value++
	defer a.lock.Unlock()
}
func (a *atomicInt) get() int {
	a.lock.Lock()
	defer a.lock.Unlock()
	return a.value
}
func main() {
	var a atomicInt
	a.increment()
	go func() {
		a.increment()
	}()
	time.Sleep(time.Millisecond)
	fmt.Println(a.get())
}
$ go run -race  atomic.go # 查看数据竞争

很多的goroutine对应到少量的物理线程中,因为物理线程是抢占式的,但是goroutine是主动交出线程控制权

并发模式
  1. 生成器/服务或者任务-句柄

    func msgGen() <-chan string {
    	c := make(chan string)
    	go func() {
    		i := 0
    		for {
    			time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
    			c <- fmt.Sprintf("message %d", i)
    			i++
    		}
    	}()
    	return c
    } // 生成器
    
    func main() {
    	m := msgGen()
    	for {
    		fmt.Println(<-m)
    	}
    }
    
    
  2. 同时等待多个服务:通过另外一个chan来接收多个chan的数据,保证充分利用资源

    func msgGen(name string) chan string {
    	c := make(chan string)
    	go func() {
    		i := 0
    		for {
    			time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
    			c <- fmt.Sprintf("service %s message %d", name, i)
    			i++
    		}
    	}()
    	return c
    }
    
    func fanIn(c1, c2 chan string) chan string {
    	c := make(chan string)
    	go func() {
    		for {
    			c <- <-c1
    
    		}
    	}()
    	go func() {
    		for {
    			c <- <-c2
    		}
    	}()
    	return c
    }
    
    func main() {
    	m1 := msgGen("service1")
    	m2 := msgGen("service2")
    	m := fanIn(m1, m2)
    	for {
    		fmt.Println(<-m)
    	}
    }
    
    
    func msgGen(name string) chan string {
    	c := make(chan string)
    	go func() {
    		i := 0
    		for {
    			time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
    			c <- fmt.Sprintf("service %s message %d", name, i)
    			i++
    		}
    	}()
    	return c
    }
    
    func fanIn(c1, c2 chan string) chan string {
    	c := make(chan string)
    	go func() {
    		for {
    			c <- <-c1
    
    		}
    	}()
    	go func() {
    		for {
    			c <- <-c2
    		}
    	}()
    	return c
    }
    
    func fanInBySelect(c1, c2 chan string) chan string {
    	c := make(chan string)
    	go func() {
    		for {
    			select {
    			case m := <-c1:
    				c <- m
    			case m := <-c2:
    				c <- m
    			}
    		}
    	}()
    	return c
    }
    
    func main() {
    	m1 := msgGen("service1")
    	m2 := msgGen("service2")
    	//m := fanIn(m1, m2)
    	m := fanInBySelect(m1, m2)
    	for {
    		fmt.Println(<-m)
    	}
    }
    
    
    1. go循环变量的问题

      func fanIn(chs ...chan string) chan string { // 不定长传参
      	c := make(chan string)
      	for _, ch := range chs {
      		chCopy := ch // 复制避免循环完之后不同的chan传入不同的goroutine
      		go func() {
      			for {
      				c <- <-chCopy
      			}
      		}()
      	}
      	return c
      }
      // 另一种写法
      func fanIn(chs ...chan string) chan string {
      	c := make(chan string)
      	for _, ch := range chs {
      		go func(in chan string) {
      			for {
      				c <- <-in
      			}
      		}(ch)
      	}
      	return c
      }
      
并发的控制
  • 非阻塞等待

  • func nonBlockingWait(c chan string) (string, bool){ //非阻塞等待
    	select{
    	case m:=<-c:
    		return m, true
    	default:
    		return "", false
    	}
    }
    
    func main() {
    	m1 := msgGen("service1")
    	m2 := msgGen("service2")
    
    	for {
    		fmt.Println(<-m1)
    		if m, ok := nonBlockingWait(m2); ok {
    			fmt.Printf(m)
    		} else {
    			fmt.Println("no message from m2")
    		}
    	}
    }
    
  • 超时机制

  • func timeoutWait(c chan string, timeout time.Duration) (string, bool) {
    	select {
    	case m := <-c:
    		return m, true
    	case <-time.After(timeout):
    		return "", false
    	}
    }
    
    func main() {
    	m1 := msgGen("service1")
    
    	for {
    		if m, ok := timeoutWait(m1, 2*time.Second); ok {
    			fmt.Printf(m)
    		} else {
    			fmt.Println("timeout")
    		}
    	}
    }
    
  • 任务中断/退出

  • func msgGen(name string, done chan struct{}) chan string {
    	c := make(chan string)
    	go func() {
    		i := 0
    		for {
    			select {
    			case <-time.After(time.Duration(rand.Intn(5000)) * time.Millisecond):
    				c <- fmt.Sprintf("service %s message %d", name, i)
    			case <-done:
    				fmt.Println("cleaning up")
    				return
    			}
    			i++
    		}
    	}()
    	return c
    }
    
    func main() {
    	done := make(chan struct{})
    	m1 := msgGen("service1", done)
    
    	for i := 0; i < 5; i++ {
    		if m, ok := timeoutWait(m1, 2*time.Second); ok {
    			fmt.Println(m)
    		} else {
    			fmt.Println("timeout")
    		}
    	}
    	done <- struct{}{}
    	time.Sleep(time.Second)
    }
    
  • 优雅退出,服务器优雅退出

  • func msgGen(name string, done chan struct{}) chan string {
    	c := make(chan string)
    	go func() {
    		i := 0
    		for {
    			select {
    			case <-time.After(time.Duration(rand.Intn(5000)) * time.Millisecond):
    				c <- fmt.Sprintf("service %s message %d", name, i)
    			case <-done:
    				fmt.Println("cleaning up")
    				time.Sleep(2 * time.Second)
    				fmt.Println("cleaning done")
    				done <- struct{}{}
    				return
    			}
    			i++
    		}
    	}()
    	return c
    }
    
    func main() {
    	done := make(chan struct{})
    	m1 := msgGen("service1", done)
    
    	for i := 0; i < 5; i++ {
    		if m, ok := timeoutWait(m1, 2*time.Second); ok {
    			fmt.Println(m)
    		} else {
    			fmt.Println("timeout")
    		}
    	}
    	done <- struct{}{}
    	<-done
    }
    

综合实例:广度优先算法

package main

import (
	"fmt"
	"os"
)

func readMaze(filename string) [][]int {
	file, err := os.Open(filename) // 打开文件
	if err != nil {
		panic(err)
	}
	var row, col int
	fmt.Fscanf(file, "%d %d", &row, &col)

	maze := make([][]int, row)
	for i := range maze {
		maze[i] = make([]int, col)
		for j := range maze[i] {
			fmt.Fscanf(file, "%d", &maze[i][j])
		}

	}
	return maze

}

type point struct {
	i, j int
}

var dirs = [4]point{
	{-1, 0},
	{0, -1},
	{1, 0},
	{0, 1},
}

func (p point) add(r point) point {

	return point{p.i + r.i, p.j + r.j}
}

func (p point) reduce(r point) point {
	return point{p.i - r.i, p.j - r.j}
}

func (p point) at(grid [][]int) (int, bool) {
	if p.i < 0 || p.i >= len(grid) {
		return 0, false
	}
	if p.j < 0 || p.j >= len(grid[p.i]) {
		return 0, false
	}
	return grid[p.i][p.j], true
}

func walk(maze [][]int, start, end point) [][]int {
	steps := make([][]int, len(maze))
	for i := range steps {
		steps[i] = make([]int, len(maze[i]))

	}
	Q := []point{start}
	for len(Q) > 0 {
		cur := Q[0]
		Q = Q[1:]
		if cur == end {
			break
		}
		for _, dir := range dirs {
			next := cur.add(dir)
			// maze at next is 0
			// and steps at next is 0
			// and next != start
			val, ok := next.at(maze)
			if !ok || val == 1 {
				continue
			}

			val, ok = next.at(steps)
			if !ok || val != 0 {
				continue
			}

			if next == start {
				continue
			}

			curStpes, _ := cur.at(steps)
			steps[next.i][next.j] = curStpes + 1
			Q = append(Q, next)
		}
	}
	return steps

}

func getRoute(steps [][]int, start, end point) []point {
	route := []point{start}
	stepNum := steps[start.i][start.j]

	for start != end {
		stepNum -= 1

		for _, dir := range dirs {
			next := start.reduce(dir)
			if next.i < 0 || next.i >= len(steps) || next.j < 0 || next.j >= len(steps[0]) {
				continue
			}

			if steps[next.i][next.j] == stepNum {
				route = append(route, next)
				start = next
				break
			}
		}
	}
	return route
}

func main() {
	maze := readMaze("maze/maze.in")
	for _, row := range maze {
		for _, col := range row {
			fmt.Printf("%d ", col)
		}
		fmt.Println()
	}
	steps := walk(maze, point{0, 0}, point{len(maze) - 1, len(maze[0]) - 1})
	for _, row := range steps {
		for _, val := range row {
			fmt.Printf("%3d", val)
		}
		fmt.Println()
	}
	fmt.Println(steps[len(maze)-1][len(maze[0])-1])
	route := getRoute(steps, point{len(steps) - 1, len(steps[0]) - 1}, point{0, 0})
	fmt.Println(route)
}

http 其他标准库

http标准库

// http客户端发送请求
//httputil可以简化工作
func main() {
	resp, err := http.Get("http://www.imooc.com")
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	s, err := httputil.DumpResponse(resp, true) // 全部打印解析里面内容,收否dump,body
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", s) // 字符串底层是utf-8编码的ascii数组,%s将整个数组进行解码然后展示

}
func main() {
	request, err := http.NewRequest(
		http.MethodGet, "http://www.imooc.com", nil)
	request.Header.Add("User-Agent",
		"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1")
	
	resp, err := http.DefaultClient.Do(request)

	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	s, err := httputil.DumpResponse(resp, true)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", s)

}
func main() {
	request, err := http.NewRequest(
		http.MethodGet, "http://www.imooc.com", nil)
	request.Header.Add("User-Agent",
		"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1")
	client := http.Client{
		CheckRedirect: func(
			req *http.Request,
			via []*http.Request) error {
			fmt.Println("Redirect:", req)
			return nil
		},
	}
	resp, err := client.Do(request)

	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	s, err := httputil.DumpResponse(resp, true)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", s)

}
// 服务端口用pprof
import (
	"goprogramming/errhanding/filelistingserver/filelisting"
	"log"
	"net/http"
	_ "net/http/pprof"
	"os"
)

# import _ "net/http/pprof"
# 前端访问/debug/pprof
# 使用go tool pprof 分析性能
$ go tool pprof http://localhost:8888/debug/pprof/profile

Json解析

Json.marshal, json.unmarshal

type Order struct {
	ID         string
	Name       string
	Quantity   int
	Totalprice float64
}

func main() {
	o := Order{
		ID:         "1234",
		Name:       "learn go",
		Quantity:   3,
		Totalprice: 30,
	}
	fmt.Printf("%v\n", o) // 打印结构体的方法
	fmt.Printf("%+v\n", o)

}

package main

import (
	"encoding/json"
	"fmt"
)

type Order struct {
	ID         string
	Name       string
	Quantity   int
	Totalprice float64
}

func main() {
	o := Order{
		ID:         "1234",
		Name:       "learn go",
		Quantity:   3,
		Totalprice: 30,
	}
	b, err := json.Marshal(o) // json序列化方法,跨语言
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", b)
}
// json.marshal只能看到结构体大写的内容,在结构体中每个字段打上json:标签名称,之后就可以获得字段值
type Order struct {
	ID         string  `json:"id"`
	Name       string  `json:"name,omitempty"` \\字段如果没有定义,则不出现
	Quantity   int     `json:"quantity"`
	TotalPrice float64 `json:"total_price"`
}

func main() {
	o := Order{
		ID:         "1234",
		Name:       "learn go",
		Quantity:   3,
		TotalPrice: 30,
	}
	b, err := json.Marshal(o)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", b)

}

package main

import (
	"encoding/json"
	"fmt"
)

type OrderItem struct {
	ID    string  `json:"id"`
	Name  string  `json:"name"`
	Price float64 `json:"price"`
}

type Order struct {
	ID         string    `json:"id"`
	Item       OrderItem `json:"item"`
	Quantity   int       `json:"quantity"`
	TotalPrice float64   `json:"total_price"`
}

func main() {
	o := Order{
		ID:         "1234",
		Quantity:   3,
		TotalPrice: 30,
		Item: OrderItem{
			ID:    "item1",
			Name:  "learn_go",
			Price: 15,
		},
	}
	b, err := json.Marshal(o)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", b)

}

# 解析json
idea:文件 -> 新建 -> 新建临时文件(scratch file)-> json
解析格式:代码 -> 重新格式化代码(ctrl + alt + l), mac: w + option + L
package main

import (
	"encoding/json"
	"fmt"
)

type OrderItem struct {
	ID    string  `json:"id"`
	Name  string  `json:"name"`
	Price float64 `json:"price"`
}

type Order struct {
	ID         string     `json:"id"`
	Item       *OrderItem `json:"item"` // 传入指针
	Quantity   int        `json:"quantity"`
	TotalPrice float64    `json:"total_price"`
}

func main() {
	o := Order{
		ID:         "1234",
		Quantity:   3,
		TotalPrice: 30,
		Item: &OrderItem{
			ID:    "item1",
			Name:  "learn_go",
			Price: 15,
		},
	}
	b, err := json.Marshal(o)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", b)

}

package main

import (
	"encoding/json"
	"fmt"
)

type OrderItem struct {
	ID    string  `json:"id"`
	Name  string  `json:"name"`
	Price float64 `json:"price"`
}

type Order struct {
	ID         string      `json:"id"`
	Item       []OrderItem `json:"items"`
	Quantity   int         `json:"quantity"`
	TotalPrice float64     `json:"total_price"`
}

func main() {
	o := Order{
		ID:         "1234",
		Quantity:   3,
		TotalPrice: 20,
		Item: []OrderItem{
			{
				ID:    "item_1",
				Name:  "learn go",
				Price: 15,
			},
			{
				ID:    "item2",
				Name:  "iterview",
				Price: 12,
			},
		},
	}
	b, err := json.Marshal(o)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", b)

}

// 解析json
type OrderItem struct {
	ID    string  `json:"id"`
	Name  string  `json:"name"`
	Price float64 `json:"price"`
}

type Order struct {
	ID         string      `json:"id"`
	Item       []OrderItem `json:"items"`
	Quantity   int         `json:"quantity"`
	TotalPrice float64     `json:"total_price"`
}
func unmarshal() {
	s := `{"id":"1234","items":[{"id":"item_1","name":"learn go","price":15},{"id":"item2","name":"iterview","price":12}],"quantity":3,"total_price":20}`
	var o Order
	err := json.Unmarshal([]byte(s), &o)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%+v\n", o)
}

第三方API数据格式的解析

func parseNLP() {
	res := `{"result":[{"synonym":"","weight":"0.100000","tag":"普通词","word":"请"},{"synonym":"","weight":"0.100000","tag":"普通词","word":"输入"},{"synonym":"","weight":"1.000000","tag":"品类","word":"文本"}],"success":true}`
	m := make(map[string]interface{})
	err := json.Unmarshal([]byte(res), &m)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%+v\n", m["result"].([]interface{})[2].(map[string]interface{})["weight"]) // 采用接口断言防止编译错误
}
func parseNLP() {
	res := `{"result":[{"synonym":"","weight":"0.100000","tag":"普通词","word":"请"},{"synonym":"","weight":"0.100000","tag":"普通词","word":"输入"},{"synonym":"","weight":"1.000000","tag":"品类","word":"文本"}],"success":true}`
	m := struct {
		Result []struct {
			Tag    string `json:"tag"`
			Weight string `json:"weight"`
		} `json:"result"`
	}{}
	err := json.Unmarshal([]byte(res), &m)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%+v,%+v\n", m.Result[2].Tag, m.Result[2].Weight)
}

http 框架/ gin

gin

$ go get -u github.com/gin-gonic/gin
$ go get -u go.uber.org/zap
package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {

		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.GET("/hello", func(c *gin.Context) {

		c.String(200, "hello")
	})
	r.Run()
}

middleware

package main

import (
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

func main() {
	r := gin.Default()
	logger, err := zap.NewProduction() // 日志
	if err != nil {
		panic(err)
	}
	r.Use(func(c *gin.Context) { // 启用中间件
		// path,log latency, response code
		logger.Info("incomming request",
			zap.String("path", c.Request.URL.Path))
		c.Next()
	})
	r.GET("/ping", func(c *gin.Context) {

		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.GET("/hello", func(c *gin.Context) {

		c.String(200, "hello")
	})
	r.Run()
}

package main

import (
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"time"
)

func main() {
	r := gin.Default()
	logger, err := zap.NewProduction()
	if err != nil {
		panic(err)
	}
	// 中间件
	r.Use(func(c *gin.Context) {
		// path,response code, latency
		s := time.Now()

		c.Next() // 执行消息
		logger.Info("incomming request",
			zap.String("path", c.Request.URL.Path),
			zap.Int("status", c.Writer.Status()),
			zap.Duration("elapsed", time.Now().Sub(s)))

	})
	r.GET("/ping", func(c *gin.Context) {

		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.GET("/hello", func(c *gin.Context) {

		c.String(200, "hello")
	})
	r.Run()
}

package main

import (
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"math/rand"
	"time"
)

func main() {
	r := gin.Default()
	logger, err := zap.NewProduction()
	if err != nil {
		panic(err)
	}
	// 中间件
	r.Use(func(c *gin.Context) {
		// path,response code, latency
		s := time.Now()

		c.Next() // 执行消息
		logger.Info("incomming request",
			zap.String("path", c.Request.URL.Path),
			zap.Int("status", c.Writer.Status()),
			zap.Duration("elapsed", time.Now().Sub(s)))

	}, func(c *gin.Context) { // context中加入东西
		c.Set("requestID", rand.Int())
		c.Next()
	})

	r.GET("/ping", func(c *gin.Context) {
		h := gin.H{
			"message": "pong",
		}
		if rid, exists := c.Get("requestID"); exists { //从context中取出东西
			h["requestID"] = rid
		}
		c.JSON(200, h)
	})
	r.GET("/hello", func(c *gin.Context) {

		c.String(200, "hello")
	})
	r.Run()
}

package main

import (
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"math/rand"
	"time"
)
const keyRequestID = "requestID"
func main() {
	r := gin.Default()
	logger, err := zap.NewProduction()
	if err != nil {
		panic(err)
	}
	// 中间件
	r.Use(func(c *gin.Context) {
		// path,response code, latency
		s := time.Now()

		c.Next() // 执行消息
		logger.Info("incomming request",
			zap.String("path", c.Request.URL.Path),
			zap.Int("status", c.Writer.Status()),
			zap.Duration("elapsed", time.Now().Sub(s)))

	}, func(c *gin.Context) {
		c.Set(keyRequestID, rand.Int())
		c.Next()
	})

	r.GET("/ping", func(c *gin.Context) {
		h := gin.H{
			"message": "pong",
		}
		if rid, exists := c.Get(keyRequestID); exists {
			h[keyRequestID] = rid
		}
		c.JSON(200, h)
	})
	r.GET("/hello", func(c *gin.Context) {

		c.String(200, "hello")
	})
	r.Run()
}

  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值