一、打包和工具链

在GO语言中,包是个非常重要的概念,其设计理念是使用包来封装不同的的语义单元的功能。这样做,能够更好的复用代码,并对每个包内的数据的使用有更好的控制。所有的GO文件,除了空行和注释,都应该在第一行声明自己所属的包。每个包都在一个单独的目录里,并且应该使用简洁、清晰且全小写的名字,这有利于开发时频繁输入包名。

1、main包
package main

// https://pkg.go.dev/fmt#pkg-functions
// 系统包会从GOROOT环境变量中去寻找,用户自定义的包会从GOPATH中寻找
import (
    "fmt"
    // 可以为指定的包自定义名字
    //String "strings"
)

// 当main包存在main方法的时候,可以使用GO build将源程序编译成二进制可执行文件
// go build main.go
func main() {

    fmt.Println("Hello!")
}
2、init函数
package main

import (
    // 我们可以使用下划线 在不导入包的情况下,调用该包的init函数
    _ "fmt"
    "log"
    "os"
)
// 每个包可以包含多个init函数,这些函数都会在程序执行开始前被调用,完成一下初始化的操作
func init(){
    // 将日志定向到标准输出
    log.SetOutput(os.Stdout)
    log.Println("init 函数被调用了!")
}

func main() {

}
4、GO提供的开发工具
# 检查代码是否存在常见错误
go vet main.go
# 格式化GO代码
go fmt main.go
# 查看文档
go doc fmt

二、数组、切片和映射

1、数组
package main

import (
    "fmt"
    "log"
    "os"
)

func init() {
    log.SetOutput(os.Stdout)
}
// 数组函数是值传递
// 使用指针优化参数传递
func passArray(arr *[1e6]int){
    log.Println(arr[1])
}

func main() {
    // 声明一个包含5个元素的整型数组,默认值是0
    var array1 [5]int
    for _, v := range array1 {
        log.Println(v)
    }
    // 声明一个包含5个元素的整型数组, 使用具体值初始化
    array2 := [5]int{10,20,30,40,50}
    for _, v := range array2 {
        log.Println(v)
    }
    // 声明一个整型数组,容量由初始化值的数量决定
    array3 := [...]int{1111,2222,3333}
    for _, v := range array3 {
        log.Println(v)
    }
    // 声明一个整型数组,容量自动计算,指定下标7和5的具体值
    array4 := [...]int{1111,7:2222,5:3333}
    for _, v := range array4 {
        log.Println(v)
    }

    //声明一个整型指针类型的数组
    array5 := [...]*int{new(int), new(int)}
    *array5[0] = 100
    *array5[1] = 200
    for _, v := range array5 {
        log.Println(*v)
    }

    // !!!! 拷贝赋值
    array6 := array3
    array6[0] = -1
    // array3 的值没有发生任何改变
    for _, v := range array3 {
        log.Println(v)
    }

    log.Println("================多维数组===============")
    // 声明一个二维整型数组,两个维度分别存储4个元素和2个元素
    var array7 [4][2]int
    for _,v1 := range array7{
        for _,v2 := range v1{
            fmt.Print(v2," ")
        }
        fmt.Println()
    }
    array8 := [4][2]int{{1,2},{3,4},{5,6},{7,8}}
    for _,v1 := range array8{
        for _,v2 := range v1{
            fmt.Print(v2," ")
        }
        fmt.Println()
    }

    log.Println("=====================================")
    var array9 [1e6]int
    for i := 0; i < 100000; i++ {
        passArray(&array9)
    }
    log.Println("=====================================")
}
2、定义切片
package main

import (
    "log"
    "os"
)

func init() {
    log.SetOutput(os.Stdout)
}
func PrintSlice(slice interface{}){
    log.Println(slice)
}
func main() {
    // 切片是一种数据结构,这种数据便于使用和管理数据集合
    // 创建一个整型切片,其长度为3个元素
    slice1 := make([]int,3)
    PrintSlice(slice1)
    // 创建一个整型切片,其长度为3个元素,容量为5个元素
    slice2 := make([]int,3,5)
    PrintSlice(slice2)
    // 不允许创建长度大于容量的切片
    // len larger than cap in make([]int)
    //slice3 := make([]int,5,3)
    //PrintSlice(slice3)
    // 通过字面量创建切片,其容量长度都是2,注:[2]string{"If","Else"}是数组
    slice4 :=[]string{"If","Else"}
    PrintSlice(slice4)
    // 通过字面量创建切片,其长度容量都是5
    // 记住,如果在[]运算符里指定了一个值,那么创建的就是数组而不是切片.只有不指定值的时候才会创建切片
    slice5 := []string{4:"Do"}
    log.Println(cap(slice5),len(slice5))
    PrintSlice(slice5)
    // 创建nil切片,当需要描述一个不存在的切片时,nil切片会很好用
    // 不管是nil还是空切片,append、len和cap效果都是一样的
    var slice6 []int
    PrintSlice(slice6)
    // 声明空切片,空切片在底层包含0个元素,也没有分配任何存储空间
    // 想表示集合空时,nil切片会很好用,例如数据库返回0个查询结果
    // 不管是nil还是空切片,append、len和cap效果都是一样的
    slice7 := make([]int,0)
    PrintSlice(slice7)

}
3、切片增长
package main

import (
    "log"
    "os"
)

func init() {
    log.SetOutput(os.Stdout)
}

func main() {
    slice :=[]int{1,2,3,4,5}
    newSlice := slice[1:3]
    // 因为newSlice在底层数组里还有可用空间,append操作将可用的元素合并到切片的长度,并对其赋值
    // 由于和原始的slice共享同一个底层数组,slice中索引为3的元素也被改变了
    newSlice = append(newSlice,100)
    log.Println(slice,newSlice)

    slice =[]int{1,2,3,4,5}
    // append操作完成后,newSlice拥有一个全新的底层数组,这个数组的容量是原来的两倍
    // append会智能地处理底层数组的容量增长。在切片容量少于1000时,总是成倍地增长容量
    // 一旦元素个数超过1000,容量的增长因子会设置为1.25,也就是每次会增长25%的容量
    newSlice = append(slice,100)
    log.Println(slice,newSlice)

}
4、限制切片容量
package main

import (
    "log"
    "os"
)

func init() {
    log.SetOutput(os.Stdout)
}
func main() {
    source :=[]int{1,2,3,4,5}
    // 第三个参数用来控制切片的容量,这里设置的容量是3
    slice := source[0:2:2]
    // 由于切片容量等于了切片的大写,append操作后会产生一个全新的底层数组给slice
    slice = append(slice, 100)
    log.Println(source,slice)

}
5、切片合并
package main

import (
    "log"
    "os"
)

func init() {
    log.SetOutput(os.Stdout)
}
func main() {
    s1 :=[]int{1,2,3,4,5}
    s2 :=[]int{10,20,30,40,50}
    // 使用...操作符,可以将一个切片的所有元素追加到另一个切片里
    // 这样就完成了切片合并的效果
    s3 := append(s1,s2...)
    log.Println(s3)

}
6、迭代切片
package main

import (
    "log"
    "os"
)

func init() {
    log.SetOutput(os.Stdout)
}
func main() {
    slice := []int{1,2,3,4,5};
    // for range 循环
    for index, value := range slice{
        // range 创建的是每个元素的副本,而不是直接返回对该元素对引用
        log.Println(index, value)
    }
    // 传统 for 循环
    for i:=0;i<len(slice);i++{
        log.Println(i, slice[i])
    }

}
7、在函数间传递切片
package main

import (
    "log"
    "os"
)

func init() {
    log.SetOutput(os.Stdout)
}

func PassSlice(slice []int) []int {
    for i,_:= range slice{
        slice[i] = slice[i] * slice[i]
    }
    return slice
}
func main() {
    slice := []int{1,2,3,4,5}
    // 在64位架构的机器上,一个切片需要24字节的内存
    // 指针字段需要8个字节,长度和容量字段分别各需要8个字节
    PassSlice(slice)
    // 2021/08/31 16:49:41 [1 4 9 16 25]
    log.Println(slice)

}
8、定义映射
package main

import (
    "log"
    "os"
)

func init() {
    log.SetOutput(os.Stdout)
}

func main() {
    // 创建一个映射,键是string,值是int
    dict1 := make(map[string]int)
    dict1["Apple"] = 1
    dict1["Orange"] = 2
    log.Println(dict1)
    // 创建映射时,更常用的方法是使用映射字面量。映射的长度会根据初始化时指定的键值对的数量来决定
    // 映射的键可以是任何值。这个值的类型可以是内置的类型,也可以是结构类型,只要这个值可以用==做比较
    // 切片、函数以及包含切片的结构类型由于具有引用语义,不能作为映射的键,使用这些类型会造成编译错误
    dict2 := map[string]string{"Red":"#da1337","Orange":"#e95a22"}
    log.Println(dict2)
    // invalid map key type []string
    // _ = make(map[[]string]string)
    // 映射的值可以是任何类型
    _ = make(map[string][]string)

}
9、判断映射中是否存在指定的键
package main

import (
    "log"
    "os"
)

func init() {
    log.SetOutput(os.Stdout)
}
func PrintSlice(slice interface{}){
    log.Println(slice)
}
func main() {
    dict := make(map[string]int)
    //dict["Key"] = 0 // 这行将造成这条语句混淆:value = dict["Key"]
    // 同时获得值和这个键是否存在的标志
    value,exists := dict["Key"]
    if exists {
        log.Println(value)
    }
    // 对于不存在的键,将返回对应的0值
    // 如果要判断是否存在某个key,使用多返回值的方法比较好
    value = dict["Key"]
    log.Println(value)
}
10、删除映射中指定键对应的键值对
package main

import (
    "log"
    "os"
)

func init() {
    log.SetOutput(os.Stdout)
}
func PrintSlice(slice interface{}){
    log.Println(slice)
}
func main() {
    dict := map[string]string{"Red":"#da1337","Orange":"#e95a22"}
    // 删除键为Red的键值对
    delete(dict,"Red")
    log.Println(dict)
    //for k,v := range dict{
    //  delete(dict,"Red")
    //  log.Println(k,v)
    //}
}
11、在函数间传递映射
package main

import (
    "log"
    "os"
)

func init() {
    log.SetOutput(os.Stdout)
}
func PrintSlice(slice interface{}){
    log.Println(slice)
}
func Remove(m map[string]string,key string){
    delete(m,key)
}
func main() {
    dict := map[string]string{"Red":"#da1337","Orange":"#e95a22"}
    // 删除键为Red的键值对
    Remove(dict,"Red")
    // 函数执行完成后,Red对应的键值对被删除了
    // 这意味这映射和切片一样,都是引用语义的
    log.Println(dict)
}

三、Go语言的类型系统

1、自定义类型
package main

import (
    "log"
    "os"
)

func init() {
    log.SetOutput(os.Stdout)
}

// User 在程序里定义一个用户类型
type User struct {
    name string
    email string
    age int
}
type Duration int64
func main() {
    // 创建一个类型为User的变量,其对应的值会用零值初始化
    var user1 User
    log.Println(user1)
    // 创建一个类型为User的变量,使用字面量初始化
    user2 := User{name: "Lisa",email: "lisa@tencent.com",age:100}
    log.Println(user2)
    // 不使用字段名,创建结构类型
    user3 :=User{"Alice","alice@tencent.com",100}
    log.Println(user3)

    var d Duration
    // 类型int64不能作为类型Duration来用
    //d = int64(111)
    log.Println(d)

}
2、指针对象方法
package main

import (
    "log"
    "os"
)

func init() {
    log.SetOutput(os.Stdout)
}

type Object struct {
    val int
}

// o称为接收者,一个函数如果有接收者,这个函数就称为方法
// 这里的o是值传递语义的,o只是调用者的副本
func (o Object) GetVal() int {
    return o.val
}
//func (o Object) SetVal(val int) {
// 这里的o是指针语义的,o是调用者的地址
func (o *Object) SetVal(val int) {
    // go 会自动为我们包装
    o.val = val
}
func main() {
    o := &Object{1}
    o.SetVal(100)
    // o.GetVal() 编译器背后会帮我们变成 (*o).GetVal()
    // 简单来说,编译器会对类型的指针做适配
    log.Println(o.GetVal())

}
3、值对象方法
package main

import (
    "log"
    "os"
)

func init() {
    log.SetOutput(os.Stdout)
}

type Object struct {
    val int
}

// o称为接收者,一个函数如果有接收者,这个函数就称为方法
// 这里的o是值传递语义的,o只是调用者的副本
func (o Object) GetVal() int {
    return o.val
}
//func (o Object) SetVal(val int) {
// 这里的o是指针语义的,o是调用者的地址
func (o *Object) SetVal(val int) {
    // go 会自动为我们包装
    o.val = val
}
func main() {
    o := Object{1}
    // o.SetVal() 编译器背后会帮我们变成 (&o).GetVal()
    // 简单来说,编译器会对类型的指针做适配
    o.SetVal(100)
    log.Println(o.GetVal())

}
4、使用值语义创建对象
package main

import (
    "log"
    "os"
)

func init() {
    log.SetOutput(os.Stdout)
}

type Object struct {
    val int
}

func (o Object) GetVal() int {
    return o.val
}
func (o *Object) SetVal(val int) {
    o.val = val
}
func main() {
    o := Object{1}
    // 因为o是值语义的,所以这里是拷贝赋值
    x := o
    // x的操作不会影响到o
    x.SetVal(100)
    log.Println(o.GetVal())
}
5、使用指针语义创建对象
package main

import (
    "log"
    "os"
)

func init() {
    log.SetOutput(os.Stdout)
}

type Object struct {
    val int
}

func (o Object) GetVal() int {
    return o.val
}
func (o *Object) SetVal(val int) {
    o.val = val
}
func main() {
    o := &Object{1}
    // 因为o是指针类型,此时的x将会与o指向同一对象
    x := o
    // x的操作将会影响到o
    x.SetVal(100)
    log.Println(o.GetVal())

}
6、普通函数适配类型
package main

import (
    "log"
    "os"
)

func init() {
    log.SetOutput(os.Stdout)
}

type X struct{
    age int
}
func PassObject(x *X){

}

func main() {
    var x X
    // 编译错误,普通函数不能自动适配值类型和指针类型
    PassObject(x)
}
7、定义和实现接口
package main

import "fmt"

type Executor interface {
    Execute()
}

type X struct {

}
// 拥有接口的方法就表示实现了该接口
func (x X) Execute()  {
    fmt.Println("X Execute ")
}

func Do(executor Executor){
    executor.Execute()
}

func main() {
    var x X
    Do(x)
}
8、值类型接收者和指针类型接收者
package main

import "fmt"

type notifier interface {
    notify()
}
type X struct {
    name string
}
func (x* X) notify()  {
    fmt.Println("Notify!")
}
func sendNotification(x notifier){

}

type duration int

func (d *duration) change()  {
    *d = 100
}

func main() {
    x := X{name: "Alice"}
    x.notify()
    // 如果使用指针接受者来实现一个接口,那么只有指向那个类型的指针才能够实现对应接口
    // 如果使用值类型来接收者来实现一个接口,那么那个类型的值和指针都能实现对应接口
    // 此时,值类型的的x并没有实现notifier接口
    // 传入地址,不再有错误
    sendNotification(&x)

    d := duration(100)
    d.change()
    // ./main.go:36:15: cannot call pointer method on duration(100)
    // ./main.go:36:15: cannot take the address of duration(100)
    // duration的值类型有时获取不到地址
    // duration(100).change()

}
9、多态
package main

import "fmt"

type Executor interface {
    Execute()
}

type X struct {

}
// 拥有接口的方法就表示实现了该接口
func (x X) Execute()  {
    fmt.Println("X Execute ")
}
type Y struct {

}
// 拥有接口的方法就表示实现了该接口
func (y Y) Execute()  {
    fmt.Println("Y Execute ")
}

func Do(executor Executor){
    executor.Execute()
}

func main() {
    var x X
    Do(x)
    var y Y
    Do(y)
}
10、内嵌类型
package main

import "fmt"

type GetVal interface {
    GetVal() int
}
type Object struct {
    val int
}

func (o Object) PrintVal()  {
    fmt.Println(o.val)
}
func (o Object) GetVal() int  {
    // 哪怕子类覆盖val变量 此方法也返回父类对val
    return o.val
}
type Derive struct {
    // 被嵌入对类型被称为新对外部类型对内部类型
    Object
    // 覆盖父类的val
    val int
}

func (d Derive) GetVal() int {
    return d.val + 100
}

func DoGetVal(g GetVal) int{
    return g.GetVal()
}

func main() {
    // 初始化含有内部类型的变量
    o := Derive{Object{val: 1},11}
    // 内部类型的方法也被提升到外部类型
    o.PrintVal()
    // 我们可以直接访问内部类型的方法
    o.Object.PrintVal()
    // 外部类型拥有GetVal的实现,所以默认不会调用内部类型的GetVal
    fmt.Println(o.GetVal())
    // 调用内部类型的GetVal
    fmt.Println(o.Object.GetVal())
    // 多态
    fmt.Println(DoGetVal(o))
    // 外部类型拥有val,所以默认不会获取内部类型的val
    fmt.Println(o.val)
    // 获取内部类型的val
    fmt.Println(o.Object.val)
}
11、未公开的标识符
// counter/counter.go
package counter

// alertCounter 是一个未公开的类型
// 当一个标识符名字以小写字母开头时,这个标识符就是未公开的
// 当一个标识符名字以大写字母开通时,这个标识符就是公开的
type alertCounter int

// main.go
package main

import (
    "GOTesting/counter"
    "fmt"
)

func main() {
    // ./main.go:9:13: cannot refer to unexported name counter.alertCounter
    // ./main.go:9:13: undefined: counter.alertCounter
    // alertCounter类型是小写字母声明的,main包引用时会报错
    counter := counter.alertCounter(100)
    fmt.Println(counter)
}
12、使用工厂函数创建未公开的类型
// counter/counter.go
package counter

// alertCounter 是一个未公开的类型
type alertCounter int

// 使用工厂函数来创建一个未公开的alertCounter
func New(value int) alertCounter {
    return alertCounter(value)
}

// main.go
package main

import (
    "GOTesting/counter"
    "fmt"
)

func main() {
    // 为什么main函数能够创建一个未公开的类型的变量
    // 1、标识符才有公开或者未公开的属性,值没有
    // 2、短变量声明操作符,有能力捕获引用的类型,并创建一个未公开的类型的变量
    // 永远不能显式创建一个未公开的类型的变量,不过短变量声明操作符可以这么做
    counter := counter.New(100)
    fmt.Println(counter)
}
13、结构体未公开成员
// counter/counter.go
package counter

type Object struct {
    Id int
    // val 变量不能访问,但是在赋值时会被拷贝过去
    val int
}

func (o *Object) SetVal(val int)  {
    o.val = val
}

// main.go
package main

import (
    "GOTesting/counter"
    "fmt"
)

func main() {
    o := counter.Object{Id: 100}
    o.SetVal(99)
    // 此时o2 拥有一份o的全部拷贝
    o2 := o
    fmt.Println(o2)

}
14、阻止拷贝
package counter

type object struct {
    Id int
    val int
}

func (o *object) SetVal(val int)  {
    o.val = val
}
func (o object) GetVal() int {
    return o.val
}
type Object struct {
    // 阻止拷贝 object里面的属性会提升
    *object
}

func NewObject(id ,val int) *Object {
    return &Object{&object{Id: id,val: val}}
}
15、NoCopy禁止拷贝
package main

import (
    "fmt"
)

type noCopy struct{}
// See https://golang.org/issues/8005#issuecomment-190753527
func (*noCopy) Lock()   {}
func (*noCopy) Unlock() {}

type Data struct {
    noCopy noCopy
    a      int32
}
func main() {

    //  使用命令:go vet main.go 检查
    d1 := Data{a: 100}
    d2 := Data{a: 100}
    // 错误:noCopy
    d3 := d2
    // 错误:noCopy
    d2 = d1
    fmt.Println(&d1, &d2, &d3)
}
16、闭包-共享变量
package main

import "fmt"
// 参考:https://www.combination.net.cn/index.php/2019/06/01/closure-and-variable/
func CreateFunc() []func() int{
    fns := make([]func() int,10)
    for i := 0; i < 10; i++ {
        fns[i] = func() int {
            i++
            return i
        }
    }
    return fns
}
func main() {
    fns := CreateFunc()
    // 和JavaScript一样 共享一个i
    fmt.Println(fns[2]())
    fmt.Println(fns[3]())
}
17、闭包-解决共享变量的问题
package main

import "fmt"
// 参考:https://www.combination.net.cn/index.php/2019/06/01/closure-and-variable/
func CreateFunc() []func() int{
    fns := make([]func() int,10)
    for i := 0; i < 10; i++ {
        fns[i] = func(i int) func() int {
            return func() int {
                return i
            }
        }(i)
    }
    return fns
}
func main() {
    fns := CreateFunc()
    fmt.Println(fns[2]())
    fmt.Println(fns[5]())
}

四、并发

1、创建GO routine
package main

import (
    "fmt"
    "log"
    "os"
    "runtime"
    "sync"
)

func init() {
    log.SetOutput(os.Stdout)
}
func main() {
    // 分配一个逻辑处理器给调度器使用
    runtime.GOMAXPROCS(1)

    // 使用信号量来等待所有routine完成
    var wg sync.WaitGroup
    wg.Add(2)

    log.Println("Start goroutines")

    go func() {
        defer wg.Done()
        for count := 0; count < 3; count++ {
            for char := 'a'; char < 'a' + 26; char++ {
                fmt.Printf("%c ", char)
            }
        }
        fmt.Println()
    }()

    go func() {
        defer wg.Done()
        for count := 0; count < 3; count++ {
            for char := 'A'; char < 'A' + 26; char++ {
                fmt.Printf("%c ", char)
            }
        }
        fmt.Println()
    }()

    log.Println("Waiting to finish")
    wg.Wait()
    log.Println("Terminating Program")

}
2、并行运行
package main

import (
    "fmt"
    "log"
    "os"
    "runtime"
    "sync"
)

func init() {
    log.SetOutput(os.Stdout)
}

func PrintPrime(prefix string,wg *sync.WaitGroup){
    defer wg.Done()
next:
    for i := 200000; i < 210000; i++ {
        for j := 2; j < i; j++ {
            if i%j == 0 {
                continue next
            }
        }
        fmt.Println(prefix," : ", i)
    }
    fmt.Println("Completed ", prefix)
}
func main() {
    // 分配所有的逻辑处理器给调度器使用
    // 这样一来两个goroutine就可以并行执行了
    runtime.GOMAXPROCS(runtime.NumCPU())

    var wg sync.WaitGroup
    wg.Add(2)
    go PrintPrime("Routine A", &wg)
    go PrintPrime("Routine B", &wg)

    wg.Wait()

}
3、原子操作、互斥锁和信号量
package main

import (
    "fmt"
    "log"
    "os"
    "runtime"
    "sync"
    "sync/atomic"
    "time"
)

func init() {
    log.SetOutput(os.Stdout)
}
var (
    counter int64
    wg sync.WaitGroup
    mutex sync.Mutex
)
func incCounter(count int){
    defer wg.Done()
    for i := 0; i < count; i++ {
        //atomic.AddInt64(&counter, 1)

        for  {
            // atomic.LoadInt64()
            currentVal := counter
            newVal := currentVal+1
            swapped := atomic.CompareAndSwapInt64(&counter,currentVal,newVal)
            if swapped{
                break
            }
        }

        //mutex.Lock()
        //counter++
        //mutex.Unlock()
    }
}
func PerformanceTest(task, routineCount int) time.Duration{
    start := time.Now()
    wg.Add(routineCount)
    for i := 0; i < routineCount; i++ {
        go incCounter(task/routineCount)
    }
    wg.Wait()
    return time.Since(start)
}

func main() {

    runtime.GOMAXPROCS(runtime.NumCPU())
    task := 10000000
    // Mutex:   140,160,318,576,605,624,623,639,630,628,653,624,701
    // Atomic:  59,204,200,195,197,195,197,197,196,197,197,196,196
    // CAS:     93,390,597,829,981,1100,1106,1100,1103,1111,1115,1118,1109
    routineCounts := []int{1,2,4,8,10,20,25,40,50,100,200,400,1000}
    for i := 0; i < len(routineCounts); i++ {
        l := PerformanceTest(task, routineCounts[i]).Milliseconds()
        fmt.Print(l,",")

    }

}
4、无缓冲管道
package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    // 无缓冲的管道:接收和写入必须同时进行,否则会阻塞等待其中一方
    c := make(chan int)
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        defer fmt.Println("Write OK")
        defer wg.Done()
        c <- 666
    }()
    go func() {
        time.Sleep(time.Second)
        defer wg.Done()
        num := <-c
        fmt.Println(num)
    }()
    wg.Wait()
}
5、有缓冲的通道
package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    // 创建一个有3个缓冲的通道
    // 无缓冲的通道保证进行发送和接收的goroutine会在同一时间进行数据交换,有缓冲的通道没有这种保证
    // 但是:  (1).当channel已满,再向里面写数据会阻塞
    //      (2).当channel为空,再向里面读数据会阻塞
    c := make(chan int, 3)
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        defer wg.Done()
        for i := 0; i < 5; i++ {
            fmt.Println("写入成功:" , i)
            c <- i
        }
    }()
    go func() {
        defer wg.Done()
        for i := 0; i < 5; i++ {
            time.Sleep(time.Second)
            fmt.Println(<-c)
        }
    }()
    wg.Wait()
}
6、关闭通道
package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    // 创建一个有3个缓冲的通道
    c := make(chan int, 3)
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        defer wg.Done()
        for i := 0; i < 5; i++ {
            fmt.Println("写入成功:" , i)
            c <- i
        }
        fmt.Println("关闭Channel" )
        // 使用close来关闭channel
        // 注意事项
        // (1).关闭的通道必须是双向的或者是仅发送的
        // (2).它应该只由发送方执行,而不是由接收方执行
        // (3).从关闭后的通道c接收最后一个值后,从c接收的任何值都将成功,不会阻塞,并返回通道元素的零值。
        // (4).通道不像文件一样需要经常去关闭,只有当你确实没有任何数据了,或者你想显式结束range循环等,才去关闭通道
        // (5).关闭通道后,无法向channel再发送数据(将引发panic错误),但可以继续接收未接收完的数据
        // (6).对于nil通道,无论收发都会被阻塞
        close(c)
    }()
    go func() {
        defer wg.Done()
        for {
            // OK为false时代表channel被关闭且没有任何数据了
            if data,ok := <- c; ok{
                time.Sleep(time.Second)
                fmt.Println(data)
            } else {
                break
            }
        }
    }()
    wg.Wait()
}
7、使用range来读取通道中的数据
package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    // 创建一个有3个缓冲的通道
    c := make(chan int, 3)
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        defer wg.Done()
        for i := 0; i < 5; i++ {
            fmt.Println("写入成功:" , i)
            c <- i
        }
        fmt.Println("关闭Channel" )
        close(c)
    }()
    go func() {
        defer wg.Done()
        // 可以使用range来读取通道中的数据,当通道关闭且数据被全部读取,循环终止
        for data := range c{
            time.Sleep(time.Second)
            fmt.Println(data)
        }
    }()
    wg.Wait()
}
8、使用select来监控多个通道的状态
package main

import (
    "fmt"
    "time"
)
func Fibonacci(c,quit chan int){
    x,y := 1,1
    for {
        fmt.Println("Selecting...")
        // 使用select来监控多个通道的状态
        select {
        case c <- x:// 如果c可写
            t := x
            x = y
            y = t + y
        case <-quit://如果quit可读
            return
        }
    }
}
func main() {
    c := make(chan int)
    quit := make(chan int)

    go func() {
        for i := 0; i < 6; i++ {
            time.Sleep(time.Second)
            fmt.Println(<-c)
        }
        quit <- 0
    }()

    Fibonacci(c,quit)
}
9、使用select来实现非阻塞读取
package main

import (
    "fmt"
    "sync"
    "time"
)

func AsyncRead(c chan int) (int, bool) {
    select {
    case x :=<- c:
        return x,true
    default:
        return 0,false
    }
}
func main() {
    c := make(chan int)
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        defer fmt.Println("Write OK")
        defer wg.Done()
        time.Sleep(time.Second * 5)
        c <- 666
    }()
    go func() {

        defer wg.Done()
        for{
            if data, b := AsyncRead(c) ; b{
                fmt.Println(data)
                break
            } else {
                fmt.Println("没有读取到")
            }
        }

    }()
    wg.Wait()
}
10、select只监控但不进行读取
package main

import (
    "fmt"
    "sync"
    "time"
)

func AsyncRead(c chan int) (int, bool) {
    select {
    case <- c: // 这里的意思只表示c通道可读,但不从c中读数据,这样造成了循环读取
        return 1,true
    default:
        return 0,false
    }
}
func main() {
    c := make(chan int)
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        defer fmt.Println("Write OK")
        defer wg.Done()
        time.Sleep(time.Second * 2)
        c <- 666
        close(c)
    }()
    go func() {

        defer wg.Done()
        for{
            if data, b := AsyncRead(c) ; b{
                fmt.Println(data)

            } else {
                fmt.Println("没有读取到")
            }
        }

    }()
    wg.Wait()
}
11、单向通道
package main

import "fmt"
// ch1是一个正常的通道,不是单向的
// var ch1 chan int
// ch2是一个单向的通道,只用于写float64的数据
// var ch2 chan <- float64
// ch3是一个单向的通道,只用于读取int数据
// var ch3  <- chan int      
func producer(out chan <- int)  {
    for i:=0; i<=10; i++ {
        out<-i*i
    }
    close(out)
}

func consumer(in <-chan int)  {
    for num := range in{
        fmt.Println("num = ", num)
    }
}

func main() {
    //创建一个双向通道ch
    ch  := make(chan int)

    go producer(ch)

    consumer(ch)
}
12、通道性能
package main

import (
    "fmt"
    "log"
    "os"
    "runtime"
    "sync"
    "sync/atomic"
    "time"
)

func init() {
    log.SetOutput(os.Stdout)
}
var (
    counter int64
    wg sync.WaitGroup
    mutex sync.Mutex
)

var c chan int64
func incCounter(count int64){
    defer wg.Done()
    for i := 0; i < int(count); i++ {
        atomic.AddInt64(&counter, 1)
        //c <- 1
        //mutex.Lock()
        //counter++
        //mutex.Unlock()
    }
}

func PerformanceTest(task, routineCount int64) time.Duration{
    start := time.Now()
    c = make(chan int64,1000000)

    //wg.Add(1)
    //go func() {
    //  defer wg.Done()
    //  for num := range c{
    //      counter += num
    //      if counter - task == 0 {
    //          close(c)
    //          counter = 0
    //          return
    //      }
    //  }
    //}()

    wg.Add(int(routineCount))
    for i := 0; i < int(routineCount); i++ {
        go incCounter(task/routineCount)
    }
    wg.Wait()
    return time.Since(start)
}

func main() {

    runtime.GOMAXPROCS(runtime.NumCPU())
    var task int64 = 1000000
    // Mutex:   4,5,9,31,41,41,40,41,40,40,41,40,41,
    // Channel: 36,38,43,62,73,69,72,70,68,77,77,77,74,
    // Atomic:  2,6,10,9,12,12,12,12,10,9,11,10,10,
    routineCounts := []int64{1,2,4,8,10,20,25,40,50,100,200,400,1000}
    for i := 0; i < len(routineCounts); i++ {
        l := PerformanceTest(task, routineCounts[i]).Milliseconds()
        fmt.Print(l,",")

    }

}
13、Mutexes are faster than channels?

Mutexes are faster than channels.

But they can't participate in select, can't participate in the way channels are multi-reader and multi-writer meaning you can get simple "load balancing" simply by having multiple things reading the channel, and if used too freely, get you back into locking/threading hell.

My rule of thumb is, if you can do what you need to do with one mutex, there's no problem with that. For instance, concurrent map access, or some integer you need atomically incremented, or other such simple things. However, you want to never try to take two locks. This includes bearing in mind that if you take a lock in your function, and call something else that takes a lock, that's two locks. Threading hell is not caused by a single lock, it is cause by trying to use more than one at a time. At that point, you generally want channels and select. They don't magically solve the problem. Technically you can still deadlock with channels and select. In fact I rather expect early Go programmers to do it a couple of times before they really internalize the general rules for channels. But they strongly afford a style of programming that has much, much less problem with threading.

Also, performance isn't everything, but I do find it helpful to bear in mind that when using any of these primitives, they aren't free, even if they are cheap, and they need to pay their way. In general the way you do that sort of thing is try to ensure that when you do take a lock or send a message over a channel, you do what you can to send as big a task or chunk of information as possible. Even in a local OS process, you need to be a bit careful not to design internal APIs in a way that two processes are constantly interacting over mutexes or channels. For instance, rather than a loop where you ask the user server for a user's real name one at a time, create an API where the user server will accept the full list and return the full return set in one shot. Even 13.2ns is a couple hundred cycles, and that number is also the best case, it can get worse if there is contention.

https://www.reddit.com/r/golang/comments/d85d0l/safe_concurrent_write_benchmark_channel_vs_mutex/

分类: 编程

1 条评论

נערות ליווי במרכז · 2022年4月20日 下午8:58

Next time I read a blog, Hopefully it does not disappoint me as much as this one. After all, Yes, it was my choice to read through, nonetheless I truly thought youd have something helpful to talk about. All I hear is a bunch of crying about something that you can fix if you were not too busy seeking attention.

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注