zoukankan      html  css  js  c++  java
  • go 语言入门



    IDE

    GoLand 收费
    LiteIDE 免费,用户体验一般

    入口函数

    可执行程序必须包含 main 函数

    如果定义了 main 或 init 函数,启动后会自动执行,不需要显示调用,init 会在 main 之前执行,不管哪个先定义

    main/init 外的代码会先执行,不管哪块代码先写

    func main() {
        fmt.Println("Hello, World!")
    }
    
    func init() {
        fmt.Println("
    init
    ")
    }
    
    var b = test()
    

    会先执行 test 再执行 init 再执行 main

    变量

    var a string
    var b string = "abc" // 声明同时初始化
    var c = "abc"        // 直接初始化可以省略类型
    
    d := "abc"           // 同时省略 var 和类型,如果 d 已经定义的话,会报错
    
    var a, b, c string   // 可以同时定义、赋值多个
    
    var (                // 定义多个类型不同的变量
        a int
        b bool
    )
    

    和其他语言不同,go 的局部变量如果定义但是没使用,也会报错

    declared and not used
    

    需要使用变量才行

    常量, 枚举, iota

    const LENGTH int = 10
    

    常量当作枚举用

    const (
        Unknown = 0
        Female = 1
        Male = 2
    )
    

    iota 对 const 计数

        const (
            a = iota   // 0
            b          // 1
            c          // 2
            d = "ha"   // "ha"   iota = 3
            e          // "ha"   iota = 4
            f = 100    // 100    iota = 5
            g          // 100    iota = 6
            h = iota   // 7
            i          // 8
        )
    

    遇到新的 const 时 iota 会重置为 0

    占位符, nil

    var a, _ = test()
    

    空用 nil 表示

    函数体外语句

    syntax error: non-declaration statement outside function body
    

    错误原因: 函数体外的每个语句,都必须是 golang 的关键字开始

    比如不能是

    a := 1
    
    test()
    

    必须用

    var a = 123
    var b = test()
    

    这样才不报错

    if、switch、type switch、select、for

    go 的 if 条件语句不需要括号

            if result > 100 {
                fmt.Printf("%d + %d = %d
    ", a, b, result)
            } else {
                fmt.Println(result)
            }
    

    switch 语法

    switch var1 {
        case val1:
            ...
        case val2:
            ...
        default:
            ...
    }
    

    type switch 可判断变量类型

      var x = test()
    
      switch x.(type) {
          case nil:
             ...
          case int:
             ...
          case func(int) float64:
             ...
          case bool, string:    // bool 或 string 类型
             ...
          default:
             ...
      }
    

    select 的所有 case 都必须是通信操作
    如果有多个 case 可以执行,随机选择一个
    如果都不可执行,那么阻塞直到某个通信可以运行

       var c1, c2, c3 chan int
       var i1, i2 int
    
       select {
           case i1 = <-c1:
               fmt.Printf("received ", i1, " from c1
    ")
           case c2 <- i2:
               fmt.Printf("sent ", i2, " to c2
    ")
           case i3, ok := (<-c3):
               if ok {
                   fmt.Printf("received ", i3, " from c3
    ")
               } else {
                   fmt.Printf("c3 is closed
    ")
               }
           default:
               fmt.Printf("no communication
    ")
       }
    

    go 没有 while 语句,只用 for

    for init; condition; post {}
    
    for condition { }
    
    for { }
    

    init; condition; post 这些条件可以是空的

    continue, break, goto

       for a < 10 {
          if a == 5 {
             continue;
          }
          a++;
       }
    

    continue 可以加 label

        re:
            for i := 1; i <= 10; i++ {
                for j := i; j <= 100; j++ {
                    if i + j == 100 {
                        continue re
                    }
                }
            }
    

    break 和 goto 的用法类似,也可以加 label

    指针

    var a int = 20
    var i *int
    
    i = &a
    fmt.Printf("%d
    ", *i)
    

    和 c 语言一样

    数组、切片、range、map

    var n [10]int
    
    for i = 0; i < 10; i++ {
        n[i] = i + 100
    }
    

    切片就是长度可变的数组

    var n []int
    var n []int = make([]int, 10)  // 初始化长度
    
    s := arr[startIndex:endIndex]
    s := arr[startIndex:]
    s := arr[:endIndex]
    
    len(n)
    

    range 可用于迭代

    nums := []int{2, 3, 4}
    sum := 0
    for _, num := range nums {
        sum += num
    }
    

    集合

    var m map[string]string      // 声明,默认是 nil
    m = make(map[string]string)  // 初始化
    
    m["key1"] = "value1"
    m["key2"] = "value2"
    
    value, ok := m ["key3"]
    if ok {
        // 存在
    } else {
        // 不存在
    }
    
    // 删除
    delete(m, "kk")
    
    // 迭代
    for k := range m {
        fmt.Println(m[k])
    }
    

    注意要用 make 初始化,不然变量是 nil

    日期格式化

    // 这个 layout 是固定的,必须用这几个数字,不然会报错,这应该是 golang 诞生的时间...
    layout := "2006-01-02 15:04:05"
    
    // 按 layout 指定的格式转换成 string
    fmt.Printf("current time is %s
    ", time.Now().Format(layout))
    
    // t, err := time.Parse(layout, "2021-08-09 12:34:27")
    var t time.Time
    var err error
    t, err = time.Parse(layout, "2021-08-09 12:34:27")   // 按 layout 指定的格式转换成 Time 类型
    if err == nil {
        fmt.Println(t.Second())
    }
    

    做法和其他语言用 YYYYMMDD 这样的做法不一样,比较直观

    struct (类)

    go 没有类,继承等概念,而是通过 struct 实现面向对象

    type student struct {
        name string
        age  int
    }
    
    func (s student) getName() string {
        return s.name
    }
    
    func (s * student) setName(name string) {
        s.name = name
    }
    
    func (this student) getAge() int {
        return this.age
    }
    
    func (this * student) setAge(age int) {
        this.age = age
    }
    
    func (this student) display() {
        fmt.Printf("name is %s, age is %d
    ", this.name, this.age)
    }
    

    可以看到数据和方法的定义是分开的,
    方法是在函数定义中加入 struct 比如 (s student),或 (s * student) 表示传引用

    初始化和使用

    s0 := student{"Li", 20}
    
    s0.display()
    
    
    s1 := student{}
    s1.name = "Wang"
    s1.age = 35
    
    s1.display()
    
    
    s2 := new(student)
    s2.name = "Zhang"
    s2.age = 30
    
    s2.display()
    
    
    s3 := new(student)
    s3.setName("Mr." + s1.getName())
    s3.setAge(s2.getAge() - 5)
    
    s3.display()
    
    
    s4 := student{name: "Han", age: 12}
    
    s4.display()
    

    和其他语言比还是很不一样

    struct tag、reflect 反射、json 序列化

    import (
        "fmt"
        "reflect"
    )
    
    /*
     * tag 用 `` 标识
     * 由多个 key-value 组成
     * key 和 value 之间用 : 隔开,不能有空格
     * value 用 "" 标识
     * 多个 key-value 之间用空格隔开
     */
    type Student struct {
        Name string `json:"name" id:"100"`
        Age  int    `json:"age" id:"101"`
    }
    
    func main() {
        student := Student{"Wang", 18}
    
        // 通过反射拿到类型
        reflectType := reflect.TypeOf(student)
        fmt.Printf("type of student is %+v
    ", reflectType)
        ageField, _ := reflectType.FieldByName("Age")
        fmt.Println(ageField.Index)
        fmt.Println(ageField.Name)
        fmt.Println(ageField.Type)
    
        // 通过反射拿到 tag
        fmt.Println(ageField.Tag.Get("json"))
        fmt.Println(ageField.Tag.Get("id"))
    
        // 通过反射拿到值
        reflectValue := reflect.ValueOf(student)
        fmt.Printf("value of student is %+v
    ", reflectValue)
        nameField := reflectValue.FieldByName("Name")
        fmt.Println(nameField)
    }
    

    结果

    type of student is main.Student
    [1]
    Age
    int
    age
    101
    value of student is {Name:Wang Age:18}
    Wang
    

    比如 json 就用了 tag 实现序列化时使用和变量名不一样的字段名

    import (
        "fmt"
        "encoding/json"
    )
    
    type Student struct {
        Name string `json:"name"`
        Age  int    `json:"age"`
    }
    
    func main() {
        student := Student{"Wang", 18}
    
        jsonStr, _ := json.Marshal(student)
        fmt.Println(string(jsonStr))
    }
    

    输出 {"name":"Wang","age":18}

    通过匿名变量实现 struct 的继承、自定义 String() 方法

    type Person struct {
        Name string
        Age int
    }
    
    type Teacher struct {
        Title string
        Class int
        Grade int
    
        Person   // 匿名, 相当于定义了 Name 和 Age 变量,如果 Teacher 本身定义了这两个变量,那挑先定义的
    }
    
    func main() {
        teacher := Teacher{}
        teacher.Name = "Li"       // Teacher 没有直接定义 Name 和 Age
        teacher.Age = 30
        teacher.Title = "senior"
        teacher.Class = 3
        teacher.Grade = 6
    
        fmt.Println(teacher)
    }
    

    结果是 {senior 3 6 {Li 30}}

    可以看到 {Li 30} 被作为一个子变量,可以自定义 String 方法输出

    import (
        "fmt"
        strconv
    )
    
    func (this Teacher) String() string {
        return "{name:" + this.Name + ", age:" + strconv.Itoa(this.Age) +
            ", title:" + this.Title + ", class:" + strconv.Itoa(this.Class) +
            ", grade:" + strconv.Itoa(this.Grade) + "}"
    }
    

    结果是 {name:Li, age:30, title:senior, class:3, grade:6}

    type 关键字

    除了定义 struct,还用于类型别名,和函数别名

    func main() {
        type myType int
        var m myType = 123
        fmt.Println(m)
    
        type myFuncType func(int) string
        var fn myFuncType = myFunc
        fmt.Println(fn(5))
    }
    
    func myFunc(n int) string {
        return fmt.Sprintf("receive %d", n)
    }
    

    其实定义 struct 也相当于是别名

    函数的引用传递

    func swap(x *int, y *int) {
        var temp int
        temp = *x
        *x = *y
        *y = temp
    }
    
    var a int = 100
    var b int= 200
    
    swap(&a, &b)
    

    struct 是值传递,如果要传 struct 的引用,同样要用指针

    func updateStudent(s *student) {
        s.age *= 2
    }
    
    updateStudent(&s0)
    

    数组默认是值传递

    func update(array [3]int)  {
        array[0] = 100
    }
    
    var array = [3]int{1, 2, 3}
    update(array)   // array 的值不会改变
    

    数组的引用传递要用指针

    func update(array *[3]int)  {
        (*array)[0] = 100
    }
    
    var array = [3]int{1, 2, 3}
    update(&array)   // array 的值会改变
    

    切片默认就是引用传递

    func update(slice []int)  {
        slice[0] = 100
    }
    
    slice := make([]int, 0)
    slice = append(slice, 1, 2, 3)
    update(slice)   // slice 的值会改变
    

    map 默认引用传递

    func update(mapVar map[string]int)  {
        mapVar["key"] = 100
    }
    
    mapVar := make(map[string]int)
    mapVar["key"] = 1
    update(mapVar)   // mapVar 的值会改变
    

    go 的引用传递和 C 差不多

    函数闭包

    一个外部函数,如果定义了内部函数,并且内部函数引用了外部函数的变量,并且外部函数返回的是内部函数的引用,这个被返回的内部函数,就是闭包

    python 的例子

    def outer(a):
        base = a
    
        def inner(n):
            return base ** n
    
        return inner
    
    f = outer(2)    ## 这个 f 就是闭包
    print(f(10))
    print(f(3))
    

    go 语言

    func outer(a int) func(n int) float64 {
        base := a
        return func(n int) float64 {
            return math.Pow(float64(base), float64(n))
        }
    }
    
    f := outer(2)          // 这个 f 就是闭包
    fmt.Println(f(10))
    fmt.Println(f(3))
    

    通过把函数当作返回类型实现

    标识符大小写

    标识符(包括常量、变量、类型、函数名、结构字段等等)

    大写字母开头的,可以被外部包的代码所使用

    小写字母开头的,对包外是不可见的,但包内可见

    package

    文件的第一行声明该文件所属的包

    package fmt
    

    通过 import 引用包

    系统自带的 package 在 GOROOT/src

    比如

    import (
        "fmt"
        "net/http"
    )
    

    可以找到目录 GOROOT/src 有

    src/
      |- fmt
      |- net
          |- http
    

    同一级目录下的所有文件必须属于同一个包,子目录下的所有文件属于另一个包

    包名通常都和目录名一样,并且用小写

    自定义 package

    src/
      |- test.go
      |- calc
          |- calc.go
              |- func.go
    

    calc.go

    package calc
    
    import "errors"
    
    func Calc(num1, num2 int, operator string) (int, error) {
        switch operator {
        case "+":
            return sum(num1, num2), nil
        case "-":
            return minus(num1, num2), nil
        default:
            return 0, errors.New("invalid operator!")
        }
    }
    

    func.go

    package calc
    
    func sum(num1, num2 int) int {
        return num1 + num2
    }
    
    func minus(num1, num2 int) int {
        return num1 - num2
    }
    

    test.go

    package main
    
    import (
        "fmt"
        "calc"
    )
    
    func main() {
        var result, _ = calc.Calc(100, 23, "+")
        fmt.Println(result)
    }
    

    可以看到,calc.go 的 Calc 函数可以直接调用 func.go 的 sum 和 minus 函数,因为都是 package calc,都是同一个包的

    但是 test.go 必须 import calc,并且只能调 calc.Calc 不能调用 calc.sum,因为它们是不同 package 的

    大写开头的标识符可以被包外引用,小写开头的标识符只能被包内引用


    但是直接运行会报错

    package calc is not in GOROOT (C:Program FilesGosrccalc)
    

    解决方案一

    go env -w GO111MODULE=off
    
    go env -w GOPATH=xxx  (xxx 就是源代码 src 的上级目录)
    

    解决方案二

    go mod init calculator    // 任意名字,在 src 目录下,会产生 go.mod 文件
    

    import 改成

    import (
        "fmt"
        "calculator/calc"
    )
    

    这样就能正常运行了

    引用的 package 里面定义 init() 会被执行

    接口

    // 定义接口
    type Shape interface {
    	display()
    	reset() 
    }
    
    // 定义结构体
    type Rect struct {
    	m float64
    	n float64
    }
    
    type Circle struct {
    	radis float64
    }
    
    // 实现接口 (值传递)
    func (this Rect) display() {
    	fmt.Printf("this is a rect : %f
    ", (this.m*2 + this.n*2))
    }
    
    func (this Rect) reset() {
    	this.m = 0
    	this.n = 0
    }
    
    // 实现接口 (引用传递)
    func (this *Circle) display() {
    	fmt.Printf("this is a circle : %f
    ", (2 * math.Pi * this.radis))
    }
    
    func (this *Circle) reset() {
    	this.radis = 0
    }
    
    
    func main() {
        // 接口对象
        var shape Shape
    
        // 初始化为实现该接口的结构体,并调用接口
        shape = Rect{5, 10}
        shape.display()
        shape.reset()
        shape.display()  // 因为是值传递,所以实际上 reset 没改变 shape 的值,display 结果没变
    
        // 初始化为实现该接口的另一结构体,并调用接口
        shape = &Circle{5}    // Circle 实现 Shape 接口用的是引用传递,所以初始化要用上 & 符号
        shape.display()
        shape.reset()    
        shape.display()  // 因为是引用传递,所以 reset 改变了 shape 的值,display 的结果改变了
    }
    

    这部分有点像 Java

    错误处理

    go 语言没有 try...catch... 命令

    而是主张把参数作为返回值,由调用者决定怎么处理

    import (
    	"errors"
    	"fmt"
    )
    
    func divide(a int, b int) (int, error) {
    	if b == 0 {
    		return -1, errors.New("除数不能为0")
    	}
    
    	return a / b, nil
    }
    
    func main() {
    	var result, err = divide(6, 0)
    	if err != nil {
    		fmt.Println(err.Error())
    		return
    	}
    
    	fmt.Println(result)
    }
    

    errors.New("除数不能为0") 返回一个实现了 Error() 接口的结构体 errorString

    package errors
    
    func New(text string) error {
    	return &errorString{text}   // errorString 通过引用传递实现 error 接口,所以初始化要加上 & 符号
    }
    
    type errorString struct {
    	s string
    }
    
    func (e *errorString) Error() string {
    	return e.s
    }
    

    error 是一个接口

    type error interface {
        Error() string
    }
    

    errors.New("除数不能为0") 不能格式化字符串,可以改成

    fmt.Errorf("除数不能为 %d", 0)
    

    可以参考 errorString 按自己的需求,自定义自己的 struct

    defer (延迟执行)

    defer 语句会被延迟处理,在 defer 所属的函数即将返回时,会将 defer 语句返序执行

    func main() {
    	fmt.Println("start")
    	defer fmt.Println("defer 1")
    	defer fmt.Println("defer 2")
    	fmt.Println("end")
    

    结果是

    start
    end
    defer 2
    defer 1
    

    可用于释放资源,比如关闭文件,释放锁等等

    f1, err1 := os.Open(filename1)
    defer f1.Close()
    f2, err2 := os.Open(filename2)
    defer f2.Close()
    
    // 执行文件操作
    

    甚至程序奔溃后还会执行

    可以简化代码,作用就类似于 java 的 final

    panic (崩溃) 和 recover (恢复)

    作用类似于 throw

    func func1() {
    	panic("unknown error occur")
    	fmt.Println("func1")
    }
    
    func func2() {
    	func1()
    	fmt.Println("func2")
    }
    
    func main() {
    	fmt.Println("start....")
    	func2()
    	fmt.Println("end")
    }
    

    结果为

    start....
    panic: unknown error occur
    
    goroutine 1 [running]:
    main.func1(...)
    	xxx/test.go:125
    main.func2()
    	xxx/test.go:130 +0x27
    main.main()
    	xxx/test.go:12 +0x8a
    exit status 2
    

    可以看到后面的 end 不会被执行

    如果希望程序能继续执行,可以用 recover,类似于 catch,并且 recover 必须写在 defer 函数里面

    func func1() {
    	panic("unknown error occur")
    	fmt.Println("func1")
    }
    
    func func2() {
    	defer func() {
    		if err := recover(); err != nil {
    			fmt.Printf("panic: %v
    ", err)
    		}
    	}()
    
    	func1()
    	fmt.Println("func2")
    }
    
    func main() {
    	fmt.Println("start....")
    	func2()
    	fmt.Println("end")
    }
    

    结果

    start....
    panic: unknown error occur
    end
    

    panic 语句被 func2 的 defer 函数里的 recover 捕获,这样 func2() 后面的代码能继续执行

    模块管理

    如下面导入三方包

    import (
        "github.com/petermattis/goid"
    )
    

    可能会报错

    test.go:8:2: no required module provides package github.com/petermattis/goid; to add it:
        go get github.com/petermattis/goid
    

    需要执行命令按照包

    go get github.com/petermattis/goid
    

    如果报错可能需要设置代理

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

    如果报冲突错误,可以直接 set 环境变量

    set GOPROXY=https://goproxy.io,direct
    

    查看 go 的环境变量

    go env
    

    再执行 get 命令就成功了

    go: downloading github.com/petermattis/goid v0.0.0-20180202154549-b0b16
    15b78e5
    go get: added github.com/petermattis/goid v0.0.0-20180202154549-b0b1615
    b78e5
    

    go.mod 会被自动改动,多了安装的这个模块

    module calculator
    
    go 1.17
    
    require github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
    

    并且会多一个 go.sum 文件

    github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
    github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
    

    get 的时候可以指定版本

    go get github.com/google/uuid@v1.0.0
    

    go.mod 变成

    module calculator
    
    go 1.17
    
    require (
        github.com/google/uuid v1.0.0 // indirect
        github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
    )
    

    go.sum 变成

    github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
    github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
    github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
    github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
    

    如果包已经存在,然后 go get 指定了新的版本,相当于升降级,取消就用 @none

    如果不指定参数,就会自动扫描代码,把需要的三方包,都添加进来,相当于对每个包做 go get 操作

    # 不指定参数,添加代码 import 的三方包
    go get
    

    依赖包被下载到 $GOPATH/pkg/mod/cache/download
    代码被下载到 $GOPATH/pkg/mod


    go.sum 和 go.mod 比多了个 hash 值,这是包的 hash 值,用于校验,防止下载的包或本地的包不正常,比如被篡改过

    go.sum 用于保证开发使用的依赖,和实际使用的依赖是一致的

    如果有配置 GOSUMDB 变量比如 GOSUMDB=sum.golang.org 那么 go 除了校验 go.sum 的值,还会去 GOSUMDB 做二次校验

    go.mod 只记录直接依赖的版本,而 go.sum 记录所有用到的依赖的版本

    go run, go build, go install

    go run 编译并运行程序,不会生成可执行文件

    go run main.go
    

    如果 main.go 直接调用同目录下另一个文件的大写开头的变量或函数

    package main
    
    func main() {
        Test()
    }
    

    其中 Test() 也是 package main 的

    package main
    
    func Test() {
    }
    

    这样直接运行 go run main.go 会报错

    .main.go:4:5: undefined: Test
    

    必须运行整个目录

    go run .
    

    这样就能运行了

    go build 会编译并生成可执行文件(如果有 main 函数的话)

    go build
    

    go install 把编译的包放到 $GOPATH/pkg 把生成的可执行文件放到 $GOPATH/pkg

    go install
    

    和 build 比多一步包编译和安装

    并发协程 goroutine

    线程是操作系统调度的最小单位,通常为了并发执行请求,会启动多个线程

    这种做法在请求少的时候没问题,当并发请求量很大时,就不适用,因为大量线程的频繁切换,会严重损耗性能

    为了解决这种问题,引入了协程的概念

    就是线程在执行一个请求中,如果遇到 IO 等操作,不是切换其他线程执行,而是可以继续执行其他就绪的请求

    就是把协程放到队列,线程空闲时就挑就绪的协程执行,遇到 IO 操作,就挂起协程,然后挑其他协程执行,挂起的协程就绪再放回队列

    这样即充分利用了多核 CPU 做并发操作,又避免了频繁切换线程,大大提高了性能和并发量

    其他语言都是通过开发三方包来实现这样的功能

    而 Go 语言天然支持协程,只需要通过 go 关键字来开启 goroutine 即可

    Go 语言能控制线程调度协程

    import (
        "syscall"
        "github.com/petermattis/goid"
    )
    
    func main() {
        fmt.Printf("main tid : [%d], gid : [%d]
    ", GetThreadId(), goid.Get())
    
        for i := 1; i <= 5; i++ {
            go gofunc(i)
        }
    
        // 如果主程序退出,协程也会退出
        time.Sleep(time.Duration(5) * time.Second)
    
        fmt.Printf("main tid : [%d], gid : [%d]
    ", GetThreadId(), goid.Get())
    }
    
    func gofunc(functionId int) {
        for i := 1; i <= 3; i++ {
            fmt.Printf("go func [%d], tid : [%d], goid : [%d]
    ",
                functionId, GetThreadId(), goid.Get())
    
            time.Sleep(time.Duration(1) * time.Second)
        }
    }
    
    func GetThreadId() int {
        var kernel32Dll *syscall.DLL
        var GetCurrentThreadIdProc *syscall.Proc
        var err error
    
        kernel32Dll, err = syscall.LoadDLL("Kernel32.dll")
        if err != nil {
            fmt.Printf("syscall.LoadDLL fail: %v
    ", err.Error())
            return -1
        }
    
        // "GetCurrentThreadId" 这个名字不能改,应该是 Kernel32.dll 里的命令
        GetCurrentThreadIdProc, err = kernel32Dll.FindProc("GetCurrentThreadId")
        if err != nil {
            fmt.Printf("kernel32Dll.FindProc fail: %v
    ", err.Error())
            return -1
        }
    
        var pid uintptr
        pid, _, err = GetCurrentThreadIdProc.Call()
    
        return int(pid)
    }
    

    结果

    main tid : [22752], gid : [1]
    go func [1], tid : [22744], goid : [19]
    go func [5], tid : [22752], goid : [23]
    go func [4], tid : [22752], goid : [22]
    go func [2], tid : [12620], goid : [20]
    go func [3], tid : [14964], goid : [21]
    go func [4], tid : [22744], goid : [22]
    go func [5], tid : [14964], goid : [23]
    go func [1], tid : [12620], goid : [19]
    go func [2], tid : [15288], goid : [20]
    go func [3], tid : [14964], goid : [21]
    go func [3], tid : [22752], goid : [21]
    go func [2], tid : [14964], goid : [20]
    go func [5], tid : [30092], goid : [23]
    go func [4], tid : [22744], goid : [22]
    go func [1], tid : [15288], goid : [19]
    main tid : [15288], gid : [1]
    

    线程和协程不是固定的一一对应关系,每个线程都可以调用任意一个协程

    main 也是一个协程,同样可以被不同的线程调用

    通道 (channel)

    channel 可以用于协程间的通信

    // 创建一个传递 int 数据的 channel, 缓冲区为 100
    ch := make(chan int, 100)
    
    // 发送消息到 channel
    ch <- msg
    
    // 从 channel 读取消息
    msg := <-ch
    

    如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值

    如果通道带缓冲区,发送方不会阻塞,除非缓冲区满了

    接收方在有值可以接收之前会一直阻塞

    package main
    
    import (
        "fmt"
        "time"
    )
    
    var channel = make(chan int, 5)
    
    func main() {
        go producer()
        go consumer()
    
        fmt.Println("start producer and consumer")
        time.Sleep(time.Duration(5) * time.Second)
    }
    
    func producer() {
        for i := 0; i <= 10; i++ {
            channel <- i
            fmt.Printf("produce %d
    ", i)
        }
    }
    
    func consumer() {
        for i := 0; i <= 10; i++ {
            msg := <-channel
            fmt.Printf("consume %d
    ", msg)
            if i%3 == 0 {
                time.Sleep(time.Duration(1) * time.Second)
            }
        }
    }
    

    结果

    start producer and consumer
    produce 0
    produce 1
    produce 2
    produce 3
    produce 4
    produce 5
    consume 0
    consume 1
    consume 2
    consume 3
    produce 6
    produce 7
    produce 8
    consume 4
    consume 5
    consume 6
    produce 9
    produce 10
    consume 7
    consume 8
    consume 9
    consume 10
    

    可以在读取数据的时候判断有没有出错

    // ok 是 bool 类型
    v, ok := <-ch
    

    关闭通道

    close(ch)
    

    遍历通道

    package main
    
    import (
        "fmt"
        "time"
    )
    
    var channel = make(chan int, 5)
    
    func main() {
        go producer()
    
        // 遍历通道
        for i:= range channel {
            fmt.Printf("consume %d
    ", i)
        }
    
        time.Sleep(time.Duration(5) * time.Second)
    }
    
    func producer() {
        for i := 0; i <= 10; i++ {
            channel <- i
            fmt.Printf("produce %d
    ", i)
        }
    
        // 关闭通道
        close(channel)
    }
    

    结果

    produce 0
    produce 1
    produce 2
    produce 3
    produce 4
    produce 5
    consume 0
    consume 1
    consume 2
    consume 3
    consume 4
    consume 5
    consume 6
    produce 6
    produce 7
    produce 8
    produce 9
    produce 10
    consume 7
    consume 8
    consume 9
    consume 10
    

    如果 producer 里面没有 close channel 的话,range 会阻塞等待下一个消息,可是已经没有线程会发消息了,会报死锁错误

    web framework

    按 github star

    • gin-gonic/gin - 51k
    • beego/beego - 26k
    • kataras/iris - 21k
    • labstack/echo - 20k
    • gorilla/mux - 15k

    gin 看起来比较流行

    go gin

    安装

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

    例子

    package main
    
    import (
        "encoding/json"
        "fmt"
        "net/http"
    
        "github.com/gin-gonic/gin"
    )
    
    func main() {
        router := gin.Default()
    
        // group 定义的 url 有相同的前缀
        v1 := router.Group("/v1")
        {
            v1.POST("/login", loginHandler)
    
            // :groupId 表示这个是必须有的变量
            // 可以匹配 /service/groupA
            // 不能匹配 /service/ 或 /service
            v1.GET("/service/:groupId", listServiceHandler)
    
            // 请求 /service/group 的时候,不会和上一个 /service/:groupId 的 handler 冲突
            v1.GET("/service/group", listGroupHandler)
    
            // url 可以有多个可变值
            v1.PUT("/service/:groupId/:serviceId", addServiceHandler)
    
            // *serviceId 表示这个是可有可无的变量
            // 可以匹配 /service/groupA/ 或 /service/groupA/serviceB
            v1.DELETE("/service/:groupId/*serviceId", deleteServiceHandler)
        }
    
        // 单独定义一个 url
        router.GET("/status", statusHandler)
    
        fmt.Println("start server")
    
        // 启动
        router.Run(":8080")
    }
    
    var db = make(map[string][]string)
    
    type User struct {
        Name     string `json:"name"`
        Password int64  `json:"password"`
    }
    
    func loginHandler(c *gin.Context) {
        // 按 json 格式获取 body 的值
        json := User{}
        c.BindJSON(&json)
    
        fmt.Println(json)
    
        // 返回状态和内容
        c.String(http.StatusOK, fmt.Sprintf("
    Hello %s
    ", json.Name))
    }
    
    func listServiceHandler(c *gin.Context) {
        // 获取路径参数
        group := c.Param("groupId")
    
        services, ok := db[group]
    
        if !ok {
            c.String(http.StatusNotFound, "group not exist")
            return
        }
    
        data, _ := json.Marshal(services)
    
        c.String(http.StatusOK, string(data))
    }
    
    func listGroupHandler(c *gin.Context) {
        // 获取 query 参数
        includeEmptyGroup := c.Query("includeEmptyGroup")
    
        keys := make([]string, 0)
        for k := range db {
            if len(db[k]) == 0 {
                if includeEmptyGroup != "yes" {
                    continue
                }
            }
    
            keys = append(keys, k)
        }
    
        data, _ := json.Marshal(keys)
    
        c.String(http.StatusOK, string(data))
    }
    
    func addServiceHandler(c *gin.Context) {
        group := c.Param("groupId")
        service := c.Param("serviceId")
    
        _, ok := db[group]
        if !ok {
            db[group] = make([]string, 0) // 初始化长度
        }
    
        db[group] = append(db[group], service)
    
        c.String(http.StatusOK, "
    add service successfully
    ")
    }
    
    func deleteServiceHandler(c *gin.Context) {
        group := c.Param("groupId")
        service := c.Param("serviceId")
    
        _, ok := db[group]
        if !ok {
            c.String(http.StatusNotFound, "group not exist")
            return
        }
    
        // 收到的 service 带有 / 符号,即 /xxxx
        fmt.Printf("receive service %s!
    ", service)
    
        service = service[1:]
    
        found := false
        if service == "" {
            delete(db, group)
            found = true
        } else {
            lenght := len(db[group])
            for i := 0; i < lenght; i++ {
                if service == db[group][i] {
                    db[group] = append(db[group][:i], db[group][i+1:]...)  // ... 是必须的
                    found = true
                    break
                }
            }
        }
    
        if found {
            c.String(http.StatusOK, "del successfully")
        } else {
            c.String(http.StatusNotFound, "service not exist")
        }
    }
    
    func statusHandler(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    }
    

    测试

    > curl -X POST localhost:8080/v1/login -d '{"name":"lin", "password":123}'
    Hello lin
    
    > curl -X PUT localhost:8080/v1/service/group_A/service_1
    > curl -X PUT localhost:8080/v1/service/group_B/service_2
    > curl -X PUT localhost:8080/v1/service/group_B/service_3
    > curl -X PUT localhost:8080/v1/service/group_C/service_4
    add service successfully
    
    > curl -X DELETE localhost:8080/v1/service/group_C/service_4
    del successfully
    
    > curl -X GET localhost:8080/v1/service/group
    ["group_A","group_B"]
    
    > curl -X GET localhost:8080/v1/service/group?includeEmptyGroup=yes
    ["group_A","group_B","group_C"]
    
    > curl -X GET localhost:8080/v1/service/group_A
    ["service_1"]
    
    > curl -X GET localhost:8080/v1/service/group_B
    ["service_2","service_3"]
    
    > curl -X GET localhost:8080/v1/service/group_C
    []
    
    > curl -X GET localhost:8080/v1/service/group_D
    group not exist
    
    > curl -X DELETE localhost:8080/v1/service/group_D/service_4
    group not exist
    
    > curl -X DELETE localhost:8080/v1/service/group_C/service_4
    service not exist
    
    > curl -X DELETE localhost:8080/v1/service/group_B/
    del successfully
    
    > curl -X GET localhost:8080/v1/service/group
    ["group_A"]
    
    > curl -X GET localhost:8080/v1/service/group?includeEmptyGroup=yes
    ["group_A","group_C"]
    
    > curl -X GET localhost:8080/status
    {"message":"pong"}
    

    和 springboot 比感觉还差了一些

  • 相关阅读:
    [原创]浅谈测试团队转型,思维模式的转变是关键
    [原创]浅谈IT人如何做理财规划
    [原创]浅谈对华为34岁以上员工“退休”
    [原创]2017年1月读书推荐
    [原创] 上海利得基金招聘测试经理/测试工程师/测试开发工程师(长期有效)
    [原创]浅谈从“述职”向“述能”转变
    IBM的IT战略规划方法论
    腾讯企业大学培训经验
    [原创]Android App上线检查checklist
    移动开发规范
  • 原文地址:https://www.cnblogs.com/moonlight-lin/p/15225532.html
Copyright © 2011-2022 走看看