分类 go 下的文章

本文及并发系列博客资料来源:udemy 上《working with concurrency in go》,Trevor Sawler老师讲课非常细致易懂,感兴趣可以去支持一下他

以一个披萨店的生产者消费者模型来讲解chan的运用,英语注释为老师的注释原文

package main

import (
    "fmt"
    "math/rand"
    "time"

    "github.com/fatih/color"
)

const NumberOfPizzas = 10

var pizzasMade, pizzasFailed, total int

// Producer is a type for structs that holds two channels: one for pizzas, with all
// information for a given pizza order including whether it was made
// successfully, and another to handle end of processing (when we quit the channel)
type Producer struct {
    data chan PizzaOrder
    quit chan chan error
}

// PizzaOrder is a type for structs that describes a given pizza order. It has the order
// number, a message indicating what happened to the order, and a boolean
// indicating if the order was successfully completed.
type PizzaOrder struct {
    pizzaNumber int
    message     string
    success     bool
}

// Close is simply a method of closing the channel when we are done with it (i.e.
// something is pushed to the quit channel)
func (p *Producer) Close() error {
    ch := make(chan error)
    p.quit <- ch
    return <-ch
}

// makePizza attempts to make a pizza. We generate a random number from 1-12,
// and put in two cases where we can't make the pizza in time. Otherwise,
// we make the pizza without issue. To make things interesting, each pizza
// will take a different length of time to produce (some pizzas are harder than others).
func makePizza(pizzaNumber int) *PizzaOrder {
    pizzaNumber++
    if pizzaNumber <= NumberOfPizzas {
        delay := rand.Intn(5) + 1
        fmt.Printf("Received order #%d!\n", pizzaNumber)

        rnd := rand.Intn(12) + 1
        msg := ""
        success := false

        if rnd < 5 {
            pizzasFailed++
        } else {
            pizzasMade++
        }
        total++

        fmt.Printf("Making pizza #%d. It will take %d seconds....\n", pizzaNumber, delay)
        // delay for a bit
        time.Sleep(time.Duration(delay) * time.Second)

        if rnd <=2 {
            msg = fmt.Sprintf("*** We ran out of ingredients for pizza #%d!", pizzaNumber)
        } else if rnd <= 4 {
            msg = fmt.Sprintf("*** The cook quit while making pizza #%d!", pizzaNumber)
        } else {
            success = true
            msg = fmt.Sprintf("Pizza order #%d is ready!", pizzaNumber)
        }

        p := PizzaOrder{
            pizzaNumber: pizzaNumber,
            message: msg,
            success: success,
        }

        return &p

    }

    return &PizzaOrder{
        pizzaNumber: pizzaNumber,
    }
}

// pizzeria is a goroutine that runs in the background and
// calls makePizza to try to make one order each time it iterates through
// the for loop. It executes until it receives something on the quit
// channel. The quit channel does not receive anything until the consumer
// sends it (when the number of orders is greater than or equal to the
// constant NumberOfPizzas).
func pizzeria(pizzaMaker *Producer) {
    // keep track of which pizza we are making
    var i = 0

    // this loop will continue to execute, trying to make pizzas,
    // until the quit channel receives something.
    for {
        currentPizza := makePizza(i)
        if currentPizza != nil {
            i = currentPizza.pizzaNumber
            select {
            // we tried to make a pizza (we send something to the data channel -- a chan PizzaOrder)
            case pizzaMaker.data <- *currentPizza:

            // we want to quit, so send pizzMaker.quit to the quitChan (a chan error)
            case quitChan := <-pizzaMaker.quit:
                // close channels
                close(pizzaMaker.data)
                close(quitChan)
                return
            }
        }
    }
}

func main() {
    // seed the random number generator
    rand.Seed(time.Now().UnixNano())

    // print out a message
    color.Cyan("The Pizzeria is open for business!")
    color.Cyan("----------------------------------")

    // create a producer
    pizzaJob := &Producer{
        data: make(chan PizzaOrder),
        quit: make(chan chan error),
    }

    // run the producer in the background
    go pizzeria(pizzaJob)

    // create and run consumer
    for i := range pizzaJob.data {
        if i.pizzaNumber <= NumberOfPizzas {
            if i.success {
                color.Green(i.message)
                color.Green("Order #%d is out for delivery!", i.pizzaNumber)
            } else {
                color.Red(i.message)
                color.Red("The customer is really mad!")
            }
        } else {
            color.Cyan("Done making pizzas...")
            err := pizzaJob.Close()
            if err != nil {
                color.Red("*** Error closing channel!", err)
            }
        }
    }

    // print out the ending message
    color.Cyan("-----------------")
    color.Cyan("Done for the day.")

    color.Cyan("We made %d pizzas, but failed to make %d, with %d attempts in total.", pizzasMade, pizzasFailed, total)

    switch {
    case pizzasFailed > 9:
        color.Red("It was an awful day...")
    case pizzasFailed >= 6:
        color.Red("It was not a very good day...")
    case pizzasFailed >= 4:
        color.Yellow("It was an okay day....")
    case pizzasFailed >= 2:
        color.Yellow("It was a pretty good day!")
    default:
        color.Green("It was a great day!")
    }
}

我们从main看起,先rand.seed初始化了随机数相关的种子,因为后面要用到随机数生成函数。

    // seed the random number generator
    rand.Seed(time.Now().UnixNano())

color包是打印多彩字体用的,不用管

    // print out a message
    color.Cyan("The Pizzeria is open for business!")
    color.Cyan("----------------------------------")

创建生产者,这里用到了一个自定义的类型Producer的指针类型(&表示取地址)

    // create a producer
    pizzaJob := &Producer{
        data: make(chan PizzaOrder),
        quit: make(chan chan error),
    }

我们来看下这个类型的定义,producer包含data和quit两个变量,data是一个接收自定义的类型pizzaorder的chan,这个pizzaorder包含三个变量,分别是订单号、信息、成功判断。这个变量的作用是接收后面做的披萨的信息
quit就比较有意思了,类型为接收chan error 的chan,我搜了下好像普罗米修斯也有类似的写法,这种写法的目的是为了让我们能够决定信息所传递的位置,可以看到下面的代码给producer添加了一个closer方法。这个变量的用处是判断producer的状态

// Producer is a type for structs that holds two channels: one for pizzas, with all
// information for a given pizza order including whether it was made
// successfully, and another to handle end of processing (when we quit the channel)
type Producer struct {
    data chan PizzaOrder
    quit chan chan error
}


// PizzaOrder is a type for structs that describes a given pizza order. It has the order
// number, a message indicating what happened to the order, and a boolean
// indicating if the order was successfully completed.
type PizzaOrder struct {
    pizzaNumber int
    message     string
    success     bool
}

// Close is simply a method of closing the channel when we are done with it (i.e.
// something is pushed to the quit channel)
func (p *Producer) Close() error {
    ch := make(chan error)
    p.quit <- ch
    return <-ch
}

接下来,用协程运行pizzera,并用一个循环来获取并判断上面定义的pizzajob里面的data并根据data做出响应的输出
我们先来看pizzera,他的参数为一个pizzaMaker *Producer,定义了一个数作为订单数并调用了makepizza()
来看这个makepizza,他接收一个整数,然后根据随机数来判断一个披萨是否制作成功,调用sleep假装制作,最后返回一个包含有这个披萨信息的pizzaorder类型的指针。如果超过了设定的最大披萨数,他会返回一个只包含订单号的&pizzaorder
来继续看pizzera,他用一个currentPizza来接住makepizza返回的指针,如果返回的这个pizzaorder里面有数据,把他发给job的data里面,但是如果job.quit有消息,那么就关闭channel并退出这个函数
然后来看消费者,取job.data这个channel里面的数据,如果没有超过披萨上限,那么根据披萨的状态来打印对应的值,否则,超出上限代表着所有的披萨都做完了,那么调用job.Close(),给quit发消息,这时正在运行的协程发现这点之后就会关闭channel并退出

    // run the producer in the background
    go pizzeria(pizzaJob)

    // create and run consumer
    for i := range pizzaJob.data {
        if i.pizzaNumber <= NumberOfPizzas {
            if i.success {
                color.Green(i.message)
                color.Green("Order #%d is out for delivery!", i.pizzaNumber)
            } else {
                color.Red(i.message)
                color.Red("The customer is really mad!")
            }
        } else {
            color.Cyan("Done making pizzas...")
            err := pizzaJob.Close()
            if err != nil {
                color.Red("*** Error closing channel!", err)
            }
        }
    }


// pizzeria is a goroutine that runs in the background and
// calls makePizza to try to make one order each time it iterates through
// the for loop. It executes until it receives something on the quit
// channel. The quit channel does not receive anything until the consumer
// sends it (when the number of orders is greater than or equal to the
// constant NumberOfPizzas).
func pizzeria(pizzaMaker *Producer) {
    // keep track of which pizza we are making
    var i = 0

    // this loop will continue to execute, trying to make pizzas,
    // until the quit channel receives something.
    for {
        currentPizza := makePizza(i)
        if currentPizza != nil {
            i = currentPizza.pizzaNumber
            select {
            // we tried to make a pizza (we send something to the data channel -- a chan PizzaOrder)
            case pizzaMaker.data <- *currentPizza:

            // we want to quit, so send pizzMaker.quit to the quitChan (a chan error)
            case quitChan := <-pizzaMaker.quit:
                // close channels
                close(pizzaMaker.data)
                close(quitChan)
                return
            }
        }
    }
}

// makePizza attempts to make a pizza. We generate a random number from 1-12,
// and put in two cases where we can't make the pizza in time. Otherwise,
// we make the pizza without issue. To make things interesting, each pizza
// will take a different length of time to produce (some pizzas are harder than others).
func makePizza(pizzaNumber int) *PizzaOrder {
    pizzaNumber++
    if pizzaNumber <= NumberOfPizzas {
        delay := rand.Intn(5) + 1
        fmt.Printf("Received order #%d!\n", pizzaNumber)

        rnd := rand.Intn(12) + 1
        msg := ""
        success := false

        if rnd < 5 {
            pizzasFailed++
        } else {
            pizzasMade++
        }
        total++

        fmt.Printf("Making pizza #%d. It will take %d seconds....\n", pizzaNumber, delay)
        // delay for a bit
        time.Sleep(time.Duration(delay) * time.Second)

        if rnd <= 2 {
            msg = fmt.Sprintf("*** We ran out of ingredients for pizza #%d!", pizzaNumber)
        } else if rnd <= 4 {
            msg = fmt.Sprintf("*** The cook quit while making pizza #%d!", pizzaNumber)
        } else {
            success = true
            msg = fmt.Sprintf("Pizza order #%d is ready!", pizzaNumber)
        }

        p := PizzaOrder{
            pizzaNumber: pizzaNumber,
            message:     msg,
            success:     success,
        }

        return &p

    }

    return &PizzaOrder{
        pizzaNumber: pizzaNumber,
    }
}

整个案例,使用go协程来运行生产者,消费者用for循环监听在生产者的channel,并控制何时给生产者发送停止信号

一次运行结果如下

go run .
The Pizzeria is open for business!
----------------------------------
Received order #1!
Making pizza #1. It will take 5 seconds....
Received order #2!
Making pizza #2. It will take 2 seconds....
*** We ran out of ingredients for pizza #1!
The customer is really mad!
Received order #3!
Making pizza #3. It will take 5 seconds....
*** We ran out of ingredients for pizza #2!
The customer is really mad!
Received order #4!
Making pizza #4. It will take 1 seconds....
Pizza order #3 is ready!
Order #3 is out for delivery!
Received order #5!
Making pizza #5. It will take 1 seconds....
Pizza order #4 is ready!
Order #4 is out for delivery!
Received order #6!
Making pizza #6. It will take 3 seconds....
Pizza order #5 is ready!
Order #5 is out for delivery!
Received order #7!
Making pizza #7. It will take 5 seconds....
*** We ran out of ingredients for pizza #6!
The customer is really mad!
Received order #8!
Making pizza #8. It will take 3 seconds....
Pizza order #7 is ready!
Order #7 is out for delivery!
Received order #9!
Making pizza #9. It will take 3 seconds....
Pizza order #8 is ready!
Order #8 is out for delivery!
Received order #10!
Making pizza #10. It will take 3 seconds....
Pizza order #9 is ready!
Order #9 is out for delivery!
Pizza order #10 is ready!
Order #10 is out for delivery!
Done making pizzas...
-----------------
Done for the day.
We made 7 pizzas, but failed to make 3, with 10 attempts in total.
It was a pretty good day!

goroutines

go的协程goroutines,通过go关键字启动,由runtime负责管理
协程是比线程更加轻量级的存在,线程的创建是需要向操作系统申请的,而协程可以由程序自己本身来控制(所以也叫用户态线程),go拥有一个主线程,一个线程上可以跑多个协程(但是串行)。协程多用来做并发

avatar

协程是不会阻塞主线程的运行的,并且同一个线程内的协程共享内存(堆共享栈不共享,跟线程一样),不需要加锁,主线程结束协程跟着退出。如下例,先打印了主线程,更改了然后协程才打印出了被主线程修改了值的word,因为要保证协程的运行,所以主线程还得睡三秒来等协程运行完毕

这里有个注意点!!!!!如果,你没有做任何措施来保证一个协程能够正常的运行(如下例的主线程睡几秒),那么,很有可能出现因为主线程退出过快而导致协程还没来得及运行也跟着退出的情况

  1. 栈:栈用于维护函数调用的上下文,包括函数的参数,局部变量等等。
  2. 堆:用来容纳程序中动态分配的内存区域,当程序使用malloc和new分配的内存就来自于堆里可执行文件映像。

    package main
    
    import (
     "fmt"
     "time"
    )
    
    func main() {
     word := "HELLO"
    
     go func() {
         time.Sleep(2 * time.Second)
         fmt.Println(word)
     }()
     fmt.Println("hello")
     word = "World"
     time.Sleep(3 * time.Second)
    }
    
    
    /*
    go run ./example.go 
    hello
    World
    */

    当需要进行协程上下文切换的时候,主线程只需要交换栈空间和恢复协程的一些相关的寄存器的状态就可以实现一个用户态的线程上下文切换,没有了从用户态转换到内核态的切换成本

从头到尾理解有栈协程实现原理

channel

channel用于两个协程之间的通信,当一方向调用channel来从另外一方获取信息的时候,他就会进入堵塞状态直到获取到需要的信息,同样的,如果发送方无人接收,他也会堵塞
channel使用chan关键字

从下例可以看到,goroutine调用wait_channel函数后,因为b接收不到信息,所以进入了阻塞状态,直到第十一行给c赋了值,管道里面有东西了,才继续运作下去

package main

import (
    "fmt"
)

func main() {
    c := make(chan bool)
    go wait_channel(c, "world")
    fmt.Println("Hello")
    c <- true
    <-c
}

func wait_channel(c chan bool, s string) {
    if b := <-c; b {
        fmt.Println(s)
    }
    c <- true
}

/*
go run ./example.go
Hello
world
*/

使用buffer可以避免发送方必须等待接收方出现才能继续的问题,但是如果缓冲区被塞满了,发送方依旧会被堵塞,如果buffer是空的,接收方也会被阻塞

我们可以使用range和for来监听一个channel,当结束的时候使用closed来关闭他

package main

import (
    "fmt"
)

func main() {
    c := make(chan string)
    go hello(c, 4)
    i := 0
    for s := range c {
        fmt.Println(s, "\n", i)
        i++
    }
    v, ok := <-c
    fmt.Println("Is Channel closed?", !ok, "values", v)
}

func hello(c chan string, n int) {
    for i := 0; i <= n; i++ {
        c <- "hello!"
    }
    close(c)
}

/*
go run ./example.go
hello! 
 0
hello! 
 1
hello! 
 2
hello! 
 3
hello! 
 4
Is Channel closed? true values
*/

select

select 会去同时监听多个channel,他会一直阻塞直到有channel ready,如果同时有多个channel满足条件,他会随机选择一个
default会在别的分支都没背触发的时候选中

package main

import (
    "fmt"
    "math/rand"
    "time"
)

var list = map[string]string{
    "zhangsan":  "nihao!",
    "lisi":      "hello~",
    "yoshinobu": "nigero!!",
}

func find_p(name string, server string, c chan string) {
    time.Sleep(time.Duration(rand.Intn(10)) * time.Second)
    c <- list[name]
}

func main() {
    rand.Seed(time.Now().UnixNano())
    v1 := make(chan string)
    v2 := make(chan string)

    name := "lisi"

    go find_p(name, "v1", v1)
    go find_p(name, "v2", v2)

    select {
    case word := <-v1:
        fmt.Println("v1 say", word, "as", name)
    case word := <-v2:
        fmt.Println("v2 say", word, "as", name)
    case <-time.After(5 * time.Second):
        fmt.Println("it's too late!")
        // default:
        //     fmt.Println("well........")
    }
}

//rand.Intn生成的是伪随机,必须用rand.Seed()并传入一个变化的值如当前的时间,否则不管运行几次都是同一个值
//Duration是time里的一种类型,多种time函数都以这个为参数,
//     type Duration int64

// const (
//     minDuration Duration = -1 << 63
//     maxDuration Duration = 1<<63 - 1
// )

// Common durations. There is no definition for units of Day or larger
// to avoid confusion across daylight savings time zone transitions.
//
// To count the number of units in a Duration, divide:
//    second := time.Second
//    fmt.Print(int64(second/time.Millisecond)) // prints 1000
//
// To convert an integer number of units to a Duration, multiply:
//    seconds := 10
//    fmt.Print(time.Duration(seconds)*time.Second) // prints 10s
//
// const (
//     Nanosecond  Duration = 1
//     Microsecond          = 1000 * Nanosecond
//     Millisecond          = 1000 * Microsecond
//     Second               = 1000 * Millisecond
//     Minute               = 60 * Second
//     Hour                 = 60 * Minute
// )


gin

https://www.cnblogs.com/jakelin/p/15604267.html
https://gin-gonic.com/zh-cn/docs/quickstart/

https://pkg.go.dev/github.com/gin-gonic/[email protected]#RouterGroup.StaticFile
可能会涉及到的其他包:
net/http
os
render(内置了
binding(内置了

解决gin无法安装问题

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.io,direct

# 生成 mod 文件(切换到模块根目录)
go mod init modulename

# 清理无效依赖,增加缺失依赖
go mod tidy

go get -u github.com/gin-gonic/gin 

简单案例

第一个程序

package main

import (
    //"fmt"
    "net/http"
    "os"

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

func main(){
    engine := gin.Default() //生成一个实例
    engine.GET("ping",func(c *gin.Context) {
        c.String(http.StatusOK,"pong") //申明一个路由,告诉gin触发的url和要显示的信息
    })
    engine.Run(port()) //运行
}

func port() string{
    port := os.Getenv("PORT") //获取名为PORT的参数
    if len(port) == 0 {
        port = "8080"
    }
    return ":" + port
}

运行这个程序,会看到程序监听在8080端口,我们用浏览器去访问8080

我们再尝试访问我们设置的ping,可以看到回复了一个我们设置的pong

avatar

加一点小细节

package main

import (
    "net/http"
    "os"

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

func port() string {
    port := os.Getenv("PORT")
    if len(port) == 0 {
        port = "8080"
    }
    return ":" + port
}

type Book struct {
    Title  string `json:"书名"`
    Author string `json:"作者"`
    Date   string `json:"出版日期,omitempty"`
    ISBN   string `json:"isbn"`
}

var Booklist = map[string]Book{
    "1101100011": {Title: "go语言圣经", Author: "Alan A. A. Donovan, Brian W. Kernighan", Date: "2016-03", ISBN: "1101100011"},
    "1145141919": {Title: "U.N", Author: "anyone", ISBN: "1145141919"},
}

func AllBooks() []Book { //把booklist从map转为切片输出
    value := make([]Book, len(Booklist))
    inx := 0
    for _, book := range Booklist {
        value[inx] = book
        inx++
    }
    return value
}

func Getbook(isbn string) (Book, bool) {
    book, isfound := Booklist[isbn]
    return book, isfound
}

func Update(isbn string, book Book) bool { //如果这本书存在,那么更新
    _, exist := Booklist[isbn]
    if exist {
        Booklist[isbn] = book
    }
    return exist
}

func Create(book Book) (string, bool) { //创建书本
    _, exist := Booklist[book.ISBN]
    if exist {
        return "", false
    } else {
        Booklist[book.ISBN] = book
        return book.ISBN, true
    }
}

func Delete(isbn string) bool {
    _, exist := Booklist[isbn]
    if exist {
        delete(Booklist, isbn)
        return true
    } else {
        return false
    }
}

func main() {
    engine := gin.Default()
    engine.GET("/ping", func(c *gin.Context) {
        c.String(http.StatusOK, "pong")
    })

    engine.GET("hello", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"message": "hello gin!"}) //type H map[string]any
    })

    engine.LoadHTMLGlob("./*.html")            //加载并渲染html,另外有个类似的 LoadHTMLFiles ,但是需要一个个列举文件
    engine.StaticFile("/ico", "./favicon.ico") //将某个文件作为静态资源访问

    engine.GET("/", func(c *gin.Context) {
        c.HTML(http.StatusOK, "index.html", gin.H{"title": "here is the main website!"}) //type H map[string]any
    })

    engine.GET("/api/books", func(c *gin.Context) { //以json格式打印所有的书
        c.JSON(http.StatusOK, AllBooks())
    })

    engine.GET(("/api/books/:isbn"), func(c *gin.Context) { //通过isbn查找书
        isbn := c.Params.ByName("isbn") //byname会返回匹配name的第一个参数的值,如果没有那就返回空字符串
           //isbn := c.Query("isbn")  //也可以使用c.Query(),用?传参,但是注意路由部分不能和其他的重复
        book, found := Getbook(isbn)
        if found {
            c.JSON(http.StatusOK, book)
        }
    })

    engine.PUT("/api/books/:isbn", func(c *gin.Context) { //如果存在,那么更新
        isbn := c.Params.ByName("isbn")
        var book Book
        if c.BindJSON(&book) == nil { //bind系列方法的目的是自动提取HTTP请求中的各种参数到结构体中,内部调用ShouldBindWith和MustBind,如果发生错误会400返回
            exist := Update(isbn, book)
            if exist {
                c.Status(http.StatusOK)
            } else {
                c.Status(http.StatusNotFound)
            }
        }
    })

    engine.POST("/api/books", func(c *gin.Context) { //上传书本
        var book Book
        if c.BindJSON(&book) == nil {
            isbn, ok := Create(book)
            if ok {
                c.Header("new-book's-location", "/api/books/"+isbn)
                c.Status(http.StatusCreated)
            } else {
                c.Status(http.StatusConflict)
            }
        }
    })

    engine.DELETE("/api/books/:isbn", func(c *gin.Context) {
        isbn := c.Params.ByName("isbn")
        status := Delete(isbn)
        if status {
            c.Status(http.StatusOK)
        } else {
            c.Status(http.StatusNotFound)
        }
    })

    engine.Run(port())
}
/*
[GIN-debug] GET    /                         --> main.main.func3 (3 handlers)
[GIN-debug] GET    /api/books                --> main.main.func4 (3 handlers)
[GIN-debug] GET    /api/books/:isbn          --> main.main.func5 (3 handlers)
[GIN-debug] PUT    /api/books/:isbn          --> main.main.func6 (3 handlers)
[GIN-debug] POST   /api/books                --> main.main.func7 (3 handlers)
[GIN-debug] DELETE /api/books/:isbn          --> main.main.func8 (3 handlers)
*/
//当然,这样写十分不安全,只有在这个练习题里面才能这么干,生产环境这么写黑客们就要笑嘻了

使用postman来测试这个api是否正常

GET获取书籍列表


通过isbn找书


通过isbn更新书本信息,要注意这里使用json格式的话得用编码后的字段


上传书本信息


删除书本
![

在docker上部署微服务

FROM golang:latest #表明使用的底层镜像
#RUN apt update && apt install git #pull下来的镜像有git就不用装
#RUN go env -w GO111MODULE=on && go env -w GOPROXY=https://goproxy.io,direct && go env -w GOSUMDB=off && go install github.com/gin-gonic/[email protected] && mkdir /go/src/mircoservice-1 2> /dev/null
RUN go env -w GO111MODULE=on && go env -w GOPROXY=https://goproxy.io,direct && go env -w GOSUMDB=off && mkdir /go/src/mircoservice-1 && cd /go/src/mircoservice-1 && git clone https://github.com/gin-gonic/gin.git && go mod init &&  go mod tidy && go get -u github.com/gin-gonic/gin
ENV SOURCES /go/src/mircoservice-1/
COPY . ${SOURCES}
RUN cd ${SOURCES} && CGO_ENABLED=0 go build ./example.go
WORKDIR ${SOURCES}
CMD ${SOURCES}example
EXPOSE 8080 #这只是一个声明,没有实际的端口映射作用

go get 国内即使设置了代理网也很差,可能得多试几次才能成功,如果换成go install会因为包不是main而编译失败退出导致镜像构建失败

[root@iZbp15ywdll3dqiq4ija6lZ go-example]# docker run -it -d -p 8080:8080 go-service:v3 
7b1e634789394d0118b1d1222bae95de4e21f1ff6d2aeb23746cde0560a948e3
[root@iZbp15ywdll3dqiq4ija6lZ go-example]# curl 127.0.0.1:8080
<h1>hello!</h1></br>
<h2>here is the main website!</h2>
<b>enjoy your day!</b></br>[root@iZbp15ywdll3dqiq4ija6lZ go-example]# 

type关键字可以用于定义任何自定义类型,比如常见的

type st1 struct{
}
type ani int

也可以用来起别名

type intalias int // intalias类型是int类型的别名

type func



讲typefunc讲的非常好的一片博客

type myFunc func(int) int的意思是,定义一个叫做myFunc的函数类型,他的格式是func(int) int,只要输入输出符合这个格式的函数,都能强制类型转换成myFunc类,如

type myFunc func(int) int
 
 
func sum10(num int) int {
    fmt.Println(num*10)
}
 
 
func main() {
    newFunc := myFunc(sum10) // 这里把sum10强制类型转换为myFunc
}

这个别名的方式可以帮助我们方便的使用接口和重载,如下

type sumable interface {  //定义了一个叫做sumable的接口,拥有sum方法
    sum(int, int) int
}
 
// myFunc继承sumable接口
type myFunc func(int) int  //定义了一个叫做myFunc的函数类型,格式为func(int) int
 
func (f myFunc) sum (a, b int) int {  //给myFunc类型增加sum实现方法,只要是myFunc类都能调用这个方法
    res := a + b
    return f(res) //这里返回f(res),即myFunc(res),也就是调用下面被强制转换成myFunc的那些函数自己本身
}
 
func sum10(num int) int {  //定义了两个函数,类型都是func(int) int,符合myFunc,可以被强制类型转换
    return num * 10
}
 
func sum100(num int) int {
    return num * 100
}
 
// icansum结构体继承sumable接口
type icansum struct {  //定义了一个叫做icansum的结构类型
    name string
    res int
}
 
func (ics *icansum) sum(a, b int) int { //给icansum结构类型添加了sum方法
    ics.res = a + b
    return ics.res
}
 
// handler只要是继承了sumable接口的任何变量都行,我只需要你提供sum函数就好
func handlerSum(handler sumable, a, b int) int {  //定义了一个handlerSum,传入了一个sumable,只要这个实例实现了sum方法就能被调用
    res := handler.sum(a, b) //调用这个实例的sum方法
    fmt.Println(res)
    return res
}
 
func main() {
    newFunc1 := myFunc(sum10) //先把sum10类型转换为myFunc,然后赋值给newFunc1
    newFunc2 := myFunc(sum100)
 
    handlerSum(newFunc1, 1, 1)    // 20 调用newFunc1的sum方法,即第八行,先a+b=2,然后再newFunc1(2),即sum10(2)
    handlerSum(newFunc2, 1, 1)    // 200
 
    ics := &icansum{"I can sum", 0} //实例化一个icansum
    handlerSum(ics, 1, 1)         // 2  调用icansum的sum方法,即27行的函数,a+b=2
}

JSON是一种受到广泛支持的语言格式,JSON里一个对象是一个字符串到值的映射,用花括号和逗号分隔,JSON在go里面可以被用来编写map和结构体,譬如

type Movie struct {
    Title  string
    Year   int  `json:"released"` //编码后名称变为released,不加默认用成员名
    Color  bool `json:"color,omitempty"`//编码后改名外加如果是空的那么不显示
    Actors []string
}

var movies = []Movie{
    {Title: "Casablanca", Year: 1942, Color: false,
        Actors: []string{"Humphrey Bogart", "Ingrid Bergman"}},
    {Title: "Cool Hand Luke", Year: 1967, Color: true,
        Actors: []string{"Paul Newman"}},
    {Title: "Bullitt", Year: 1968, Color: true,
        Actors: []string{"Steve McQueen", "Jacqueline Bisset"}},
    // ...
}

使用json.Marshal和json.MarshalIndent,前者返回一个slice,没有缩进,后者带两个参数,可以控制每一行的输出的前缀和每一段的缩进

data, err := json.MarshalIndent(movies, "", "    ")
if err != nil {
    log.Fatalf("JSON marshaling failed: %s", err)
}
fmt.Printf("%s\n", data)

输出

[
    {
        "Title": "Casablanca",
        "released": 1942,
        "Actors": [
            "Humphrey Bogart",
            "Ingrid Bergman"
        ]
    },
    {
        "Title": "Cool Hand Luke",
        "released": 1967,
        "color": true,
        "Actors": [
            "Paul Newman"
        ]
    },
    {
        "Title": "Bullitt",
        "released": 1968,
        "color": true,
        "Actors": [
            "Steve McQueen",
            "Jacqueline Bisset"
        ]
    }
]

解码使用unmarshaling解码成切片,可以选择性解码,即只解出需要的值

var titles []struct{ Title string }
if err := json.Unmarshal(data, &titles); err != nil {
    log.Fatalf("JSON unmarshaling failed: %s", err)
}
fmt.Println(titles) // "[{Casablanca} {Cool Hand Luke} {Bullitt}]"