go mod init app-name
go run index.go
go get example.com/pkg ## 下载依赖
go mod tidy ## 检测该文件夹目录下所有引入的依赖,写入 go.mod 文件
go mod download ## 下载mod中的依赖
go env ## 环境变量
## GOROOT go的安装目录
## GOPATH go的工作目录
## GOPROSY go modules的代理地址,默认https://goproxy.io
## GOPRIVATE 引用内网包是,可以设置成内网代码仓库域名,只是go modules不去代理上下载

概念

  • orm框架:对象-关系映射(Object-Relational Mapping,简称ORM)。ORM框架就是连接数据库的桥梁,是一个数据持久层框架。常见的ORM框架有MyBatis, Hibernate
  • RPC 场景:Remote Procedure Call ,即远程过程调用。能使应用像调用本地方法一样的调用远程的过程或服务,可以应用在分布式服务、分布式计算、远程服务调用等许多场景

# slice

# slice与array的区别

// slice
var s []int
s := make([]int, len, cap)

// array
var a [length]int

// slice底层结构
type slice struct {
  array unsafe.Pointer
  len  	int
  cap 	int
}

注意点:

  • array需要指明长度,且为常量不可变
  • array长度为其类型中组成部分
  • array作为函数参数的时候会产生copy
  • go所有函数参数都是值传递

# len cap的变化

// len cap
var s []int 		 									// 0,0
s = append(s, 0) 									// 1,1
s = append(s, 1)									// 2,2
s = append(s, 2)									// 3,4
for i := 3; i < 1025; i++ {				// 1025, 1280
  s = append(s, i)
} 																

// 当cap<1024时候,每次*2
// 当cap>=1024时候,每次*1.25

# slice技巧

/* 
	循环赋值:
	预先分配内存可以提升性能
	直接使用index赋值而不是append可以提升性能
*/
var s []int
for j:=0; j<10000; j++{
  s=append(s, j)
}
// 性能较好
s := make([]int, 0, 10000)
for j:=0; j<10000; j++{
  s=append(s, j)
}

// 性能最好的
s := make([]int, 10000)
for j:=0; j<10000; j++{
  s[j]=j
}

# 作为参数传递的注意点

// case1:虽然函数的参数是值传递,但是s中的值是一个指针
func modifySlice(s []int) {
  s[0]=1024
}
func main() {
  var s []int
  for i:=0; i<3; i++ {
    s=append(s, i)
  }
  modifySlice(s)
  fmt.Println(s) // [1024 1 2]
}

// case2:外面的s与函数内的s不是同一个struct。传进函数的是一个struct,且按值传递,所以里面对s进行append是无法改变外面s的len
func modifySlice(s []int) {
  s=append(s, 2048)
  s[0]=1024
}
func main() {
  var s []int
  for i:=0; i<3; i++ {
    s=append(s, i)
  }
  modifySlice(s)
  fmt.Println(s) // [1024 1 2]
}

// case3:当添加4096时,重新分配了内存,这样函数中的s整体指向了其他的内存,再修改s外面的变量已不受影响了
func modifySlice(s []int) {
  s=append(s, 2048)
  s=append(s, 4096)
  s[0]=1024
}
func main() {
  var s []int
  for i:=0; i<3; i++ {
    s=append(s, i)
  }
  modifySlice(s)
  fmt.Println(s) // [0 1 2]
}

// case4
func modifySlice(s []int) {
  s=append(s, 2048)
  s=append(s, 4096)
  s[0]=1024
}
func main() {
  var s []int
  for i:=0; i<3; i++ {
    s=append(s, i)
  }
  modifySlice(s)
  fmt.Println(s) // [1024 1 2]
}

# 变量声明差异

使用[]Type{}或者make([]Type)初始化后,slice不为nil

使用var x []Type后,slice为nil

// case1
func main() {
  var s []int
  b, _ := json.Marshal(s)
  fmt.Println(string(b)) // null
}

// case2
func main() {
  s := []int{}
  b, _ := json.Marshal(s)
  fmt.Println(string(b)) // []
}

# 优化技巧

func normal(s []int){
  i:=0
  i+=s[0]
  i+=s[1]
  i+=s[2]
  i+=s[3]
  i+=s[4]
  println(i)
}

===> 编译器实现优化 bce

func bce(s []int){
  _ = s[3]
  i:=0
  i+=s[0]
  i+=s[1]
  i+=s[2]
  i+=s[3]
  i+=s[4]
  println(i)
}

# map

// map实际上的值是指针,传的参数是指针。修改会影响整个map
// map的key value都不可取地址,因为它们随着map扩容地址会改变
// map存的是值,会发生copy
// map赋值的时候会自动扩容,但随着删除不会缩容

# channel

make(chan Type)
make(chan Type, len)

func main(){
  ch:=make(chan int)
  go func() {
    ch <- 1
    close(ch)
  }
  for {
    select {
      case i:= <-ch:
      	fmt.Println(i)
      default: 
      	break
    }
  }
}
// 死循环 一直输出0
// for + select closed channel 会造成死循环
// select 重break无法跳出for循环

# 可读性

func aFunc() error {
  if err:=doSomething(); err != nil {
    return err
  }
  if err:=doAnotherthing(); err != nil {
    return err
  }
  return nil // happy path
}


var a int
if flag{
  a=1
}else{
  a=-1
}
===>
a:=-1
if flag{
  a=1
}

# 最佳实践

// 缩小锁持有时间,缩小临界区
var Users = map[string]string {
  "user": "password"
}
var mu sync.Mutex

func CheckUser(name, password string) bool {
  mu.Lock()
  defer mu.Unlock()
  realPwd, exist := Users[name]
  return exist && realPwd == password
}
===>
func CheckUser(name, password string) bool {
  var realPwd string
  var exist bool
  func(){
    mu.Lock()
  	defer mu.Unlock()
  	realPwd, exist := Users[name]
  }()
  return exist && realPwd == password
}

变量定义

var 或者直接 :=

数组是有长度的

// 定义
var a [3]int                    // 定义长度为3的int型数组, 元素全部为0
var b = [...]int{1, 2, 3}       // 定义长度为3的int型数组, 元素为 1, 2, 3
var c = [...]int{2: 3, 1: 2}    // 定义长度为3的int型数组, 元素为 0, 2, 3
var d = [...]int{1, 2, 4: 5, 6} // 定义长度为6的int型数组, 元素为 1, 2, 0, 0, 5, 6



// 遍历
for i, v := range e {  // e可以是数组,也可以是指针(var e = &b
    fmt.Printf("b[%d]: %d\n", i, v)
}
for range e {
    fmt.Println("hello")
}
for i := 0; i < len(e); i++ {
    fmt.Printf("c[%d]: %d\n", i, e[i])
}

字符串支持切片操作。

切片是简化版的动态数组

len返回切片有效长度,cap返回切片容量。

append的操作会导致重新分配内存

copy(target, origin)

var (
    a []int               // nil切片, 和 nil 相等, 一般用来表示一个不存在的切片
    b = []int{}           // 空切片, 和 nil 不相等, 一般用来表示一个空的集合
    c = []int{1, 2, 3}    // 有3个元素的切片, len和cap都为3
    d = c[:2]             // 有2个元素的切片, len为2, cap为3
    e = c[0:2:cap(c)]     // 有2个元素的切片, len为2, cap为3
    f = c[:0]             // 有0个元素的切片, len为0, cap为3
    g = make([]int, 3)    // 有3个元素的切片, len和cap都为3
    h = make([]int, 2, 3) // 有2个元素的切片, len为2, cap为3
    i = make([]int, 0, 3) // 有0个元素的切片, len为0, cap为3
)

// 切片操作

// 结尾追加
var a []int
a = append(a, 1)               // 追加1个元素
a = append(a, 1, 2, 3)         // 追加多个元素, 手写解包方式
a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包

// 开头追加
var a = []int{1,2,3}
a = append([]int{0}, a...)        // 在开头添加1个元素
a = append([]int{-3,-2,-1}, a...) // 在开头添加1个切片

// 中间位置插入元素
a = append(a, x...)       // 为x切片扩展足够的空间
copy(a[i+len(x):], a[i:]) // a[i:]向后移动len(x)个位置
copy(a[i:], x)            // 复制新添加的切片

// 删除切片
a = []int{1, 2, 3}
a = a[:len(a)-1]   // 删除尾部1个元素
a = a[:len(a)-N]   // 删除尾部N个元素
a = a[1:] // 删除开头1个元素
a = a[N:] // 删除开头N个元素
a = append(a[:0], a[1:]...) // 删除开头1个元素
a = append(a[:0], a[N:]...) // 删除开头N个元素
a = a[:copy(a, a[1:])] // 删除开头1个元素
a = a[:copy(a, a[N:])] // 删除开头N个元素
a = append(a[:i], a[i+1:]...) // 删除中间1个元素
a = append(a[:i], a[i+N:]...) // 删除中间N个元素
a = a[:i+copy(a[i:], a[i+1:])]  // 删除中间1个元素
a = a[:i+copy(a[i:], a[i+N:])]  // 删除中间N个元素

e = c[0:2:cap(c)]中,实际上是指定切片的三个属性,

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

通过移动三个指针的值,而改变切片。所以这样改不会像append一样创建新的切片。

# 语法

# 变量定义

  • 布尔类型:true、false

  • 整数类型:int、int8、int32、int64(前面加u表示有符号)

  • 浮点类型:float32、float64、complex64、complex128(complex是复数类型)

  • 指针类型:uintptr

  • byte:字符型(8位)

  • rune:字符型(32位)

// 包内变量
var (
	aa = 3
  bb = false
)

func main(){
  // 函数内定义
  var x string
  var a,b,c int = 3,4,5 
  d, e, f := 3,123, true
	fmt.Println(a, b, c, d, e, f, x)
}

i等于根号-1,那么i就是一个虚数

复数:3 + 4i

|3 + 4i|= 根号下3的平方加4的平方

通过int()、float()等方法进行类型转换

// 遍历中文
s := "hello,你好呀"
for i,ch := range []rune(s) {
  fmt.Printf("(%d %c)", i, ch)
}

使用utf8.RuneCountInString获得字符数量

# 常量与枚举

通过const定义常量,如果不规定类型,const定义的常量可作为各种类型使用

// 普通常量
const name string = "123" 
// 普通枚举:一组常量
const (
	cpp = 0
  java = 1
  python = 2
)
// 递增枚举:iota代表下面的几个变量从0值递增
const (
	cpp = iota
  _
  python
  javascript
)

# 常见语句

// 条件语句
func fn1(v int) int {
  // 一般条件语句
  if v > 100 {
    return 100
  } else if v < 0 {
    return 0
  } else {
    return v
  }
  // 加入执行条件
  if v = 100;v > 100 {
    return 100
  } else {
    return v
  }
}
func eval(a,b int,op string) int {
	// 如果switch后面不写op,可直接在case后写条件,例如op>10
  var result int
  switch op {
    case "+":
    	result = a+ b
    case "-":
    	result = a - b
    case "*":
    	result = a*b
    case "/":
    	result = a / b
    default:
    	panic("n" + op)
  }
  return result
}

// 循环语句
sum := 0
for i := 1; i <= 100; i++ {
  sum += i
}

# 函数

// 一般函数
func eval(a, b int, op string) int {
  //...
}

// 返回多个值(通常第二个值返回error)
func div(a, b int) (int, int){
  return a/b, a%b
}

// 多个传入值
func sum(numbers ...int) int {
  return 1;
}

# 指针

在go中,只有值传递一种方式,通过配合指针实现引用传递

var a int = 2
var pa *int = &a
*pa = 3
fmt.Println(a)

// 交换两个值
func swap(a, b *int) {
  *b, *a = *a, *b
}
a, b := 3, 4
swap(&a, &b)
fmt.Println(a, b)

值传递与引用传递:

将变量传入函数时,值传递是拷贝了一份值给函数中的变量,而引用传递则直接传递了引用,可在函数中操作函数外的变量

# 数据结构的定义

make 用来为 slice (opens new window),map 或 chan 类型分配内存和初始化一个对象,跟 new 类似,第一个参数是一个类型而不是一个值,跟 new 不同的是,make 返回类型的结构实列而不是指针,而返回值也依赖于具体传入的类型

var slice_ []int = make([]int,5,10)
fmt.Println(slice_)
 
var m_ map[string]int = make(map[string]int)
m_["one"] = 1
fmt.Println(m_)
 
c := make(chan int)
go func() {
  for {
    n := <-c
		fmt.Println(n)
  }
}
c <- 1
c <- 2
time.Sleep(time.Millisecond)
// 打印结果:
// [0 0 0 0 0]
// map[one:1]
// 1
// 2

# 数组

当数组被当参数传入函数中的时候,仍然是拷贝一个同样的值去进行操作

var arr1 [5]int //默认为0
arr2 := [3]int {1,2,3} // 通过:=赋值必须有初值
arr3 := [...]int {2,4,6,8,10}
var grid [4][5]int

// 遍历数组
for i, v := rang arr3 {
  fmt.Println(i, v) // 下标和值
}

# 切片(Slice)

slice是对某个数组的一个view,也就是说s1是arr的切片,在s1改变的同时,arr也会发生改变。当slice作为参数传入函数中后操作,也会改变对应数组的值。

arr := [...]int{0,1,2,3,4,5,6,7}
s := arr[2:6] //[2,3,4,5] s即为切片

// 用[]int里面不加数字表示切片类型的参数
func fn(arr []int) {}

// slice是可以向后拓展的,但不能向前拓展
arr := [...]int{0,1,2,3,4,5,6,7}
s1 := arr[2:6] // [2,3,4,5]
s2 := s1[3:5] // [5,6]
fmt.Println(len(s1),cap(s1)) // 4 6

// 增加操作 s4 and s5 no longer view arr
arr := [...]int{0,1,2,3,4,5,6,7}
s1 := arr[2:6] // [2,3,4,5]
s2 := s1[3:5] // [5,6]
s3 := append(s2, 10)// [5,6,10]
s4 := append(s3, 11)// [5,6,10,11] 
s5 := append(s4, 12)// [5,6,10,11,12]
arr // [0,1,2,3,4,5,6,10]

// 直接创建slice
var s []int // zero value for slice is nil

s1 := []int{2,4,6,8}

s2 := make([]int, 16, 32) // 直接创建长度为16,cap为32的slice

// 拷贝
copy(s2, s1) //将s1中的值拷贝如s2

// 删除
s2 = append(s2[:3], s2[4:]) // 删除下标为3的元素
s2 = s2[1:] // 删除首元素
s2 = s2[:len(s2) - 1] // 删除尾元素

# Map

map使用哈希表,必须可以比较相等,除了slice,map,function的内建类型都可以作为key,struct类型不包含上述字段,也可以作为key

m := map[string]string {
  "name": "xwx",
  "age": "11",
}
m2 := make(map[string]int) // 定义空map m2 == empty map
var m3 map[string]stirng // 定义空map m3 == nil

// 遍历

for k,v := range m {
  fmt.Println(k,v)
}

// 查询 当查询的值不存在是,获得value初始值
name, ok := m["name"] //ok表示name是否存在与m

// 删除
delete(m, "name")

# 面向【还没有的】对象

go语言只支持封装,不支持继承和多态。他并没有面向对象的很多特征,但是使用它独有的特征可以模拟使用面向对象的思想

# 结构体

// 结构体的定义
type treeNode struct{
  value int
  left, right *treeNode
}
// 实例的声明
func main() {
  var root treeNode
  root = treeNode{value: 3}
  root.left = &treeNode{}
  root.right = &treeNode{5, nil, nil}
  root.right.left = new(treeNode)
  fmt.Println(root)
  
  nodes := []treeNode {
    {value: 3},
    {},
    {6, nil, &root}
  }
}

// 为结构体定义方法 (因为go语言只有值传递,如果不加*,那么无法在方法中操作实例本身)
func (node treeNode) print() {
  fmt.Print(node.value)
}
func (node *treeNode) setValue(value int) {
  node.value = value;
}
func (node *treeNode) traverse() {
  if node == nil {
    return 
  }
  node.left.traverse()
  node.print()
  node.right.traverse()
}
root := treeNode{value: 3}
root.print();

// 用工厂函数模拟构造函数
func createTreeNode(value int){
  return &treeNode{value: value}
}

# 封装与包

在go文件的最开头通过package packageName去定义包名,一个目录下只能有一个包,但是多个文件可以都叫一个包名,这代表他们属于同一个包。

变量及函数的命名习惯

  • 名字一般使用驼峰式写法
  • 首字母大写通常为public
  • 首字母小写通常为private

通常来说,我们为结构定义的方法必须放在同一个包内。但是如果包是别人写的,我们需要拓展它,则通过以下方式

// 扩充系统类型或其他类型有两种方法:1、定义别名。2、使用组合。3、内嵌
// 组合
import "tree"
type myTreeNode struct {
  ndoe *tree.treeNode
}
func (myNode *myTreeNode) postOrder() {
  if myNode == nil || myNode.node == nil {
    return 
  }
  left := myTreeNode{myNode.node.Left}
  left..postOrder()
  right := myTreeNode{myNode.node.Right}
  right.postOrder()
  myNode.node.Print();
}
func main(){
  root = tree.treeNode{value: 3}
  myRoot := myTreeNode{&root}
  myRoot.postOrder()
}
// 别名
type NewTree tree.treeNode

func (node *NewTree) handle(v int){
  //...
}
// 内嵌 Embedding
import "tree"
type myTreeNode struct {
  *tree.treeNode
}

func (myNode *myTreeNode) getValue() {
  // 通过Node来获取未命名的值 也可以直接myNode.value获取
  return myNode.Node.value 
}

# 面向接口

resp, err := http.Get(rul)
result, err := httputil.DumpResponse(res, true)
resp.Body.Close()
string(result)

# 接口的目的

定义一个结构的类型,我们可以只定义所需要的属性或者方法,然后用它进行类型说明,从而降低代码耦合

type fnType interface {
  Get(string) string
}

上面的结构体他可以表示任何拥有一个【传入string类型返回string类型的】一个结构体

func getRetriever() fnType {
	return testing.Retriever{}
}
func main() {
  var r fnType = getRetriever()
  r.Get("balabal")
}

# 接口的定义

golang中,接口由使用者定义

// mock.go
package mock
type Retriever struct {
  Contents string
}
func (r Retriever) Get(url string) string {
  return r.Contents
}
// main.go
import "mock"
type Retriever interface {
  Get(url string) string
}
func download(r Retriever)string {
  return r.Get("lala")
}
func main(){
  var r Retriever
  r = mock.Retriever{"hahaha"}
  fmt.Println(download(r))
}

我们用Retriever声明了r变量(接口变量),变量rRetriever类型的接口变量,他其实有两部分组成,一部分是实现者的类型,另一部分则是实现者的值(实现者的指针)。

可以通过下面代码验证

// Type assertion
if mockRetriever, ok := r.(mock.Retriever); ok {
  fmt.Println(mockRetriever.Contents)
}

// switch
switch v := r.(type) {
	case mock.Retriever:
		fmt.Println(v.Contents)
}
mockRetriever := r.(mock.Retriever)
fmt.Println(mockRetriever.Contents)

使用interface{}表示任何类型

# 接口的组合

例如我同时需要用到两个接口

type Poster interface {
  Post() string
}
type Retriever interface {
  Get() string
}
type RetrieverPoster interface {
  Retriever
  Poster
}
func session(s RetrieverPoster) {
  s.Get()
  s.Post()
}

# 常用系统接口

1、Stringer

他不是一个具体的类型,而是一个概念。他的定义为

type Stringer interface {
  String() stirng
}

也就是说只要我们定义的结构体实现了String方法,那么他就是一个Stringer。

将一个结构体定义为Stringer的作用是,当我们为某一个结构体实现了String方法,在通过f m t.Println输出的时候就会自动调用此方法,类似js中的toStirng。

type A struct {
  Contents stirng
}
func (a *A) String() string {
  return fmt.Sptintf(
  	"输出的值:%s",a.Contents
  )
}

2、Reader

type Reader interface {
  Read(p []byte) (n int, err error)
}

Fscanf方法的第一个参数是Reader

3、Writer

type Writer interface {
  Write(p []byte) (n int, err error)
}

Fprintf方法的第一个参数是一个Writer

# 函数式编程

标准的函数编程规范:(实际工作中并不需要一定遵守:)

  • 不可变性:能不能有状态,只有常量和函数
  • 函数只能有一个参数

# 闭包

将函数中定义的变量长久的保存并在外部使用的现象

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(a(i))
  }
}
// 斐波那契数列
func fibonacci() func() int {
  a, b := 0, 1
  return func() int {
    a, b = b, a + b
    return a
  }
}

func main() {
  f := fibonacci()
  f() // 1
  f() // 1
  f() // 2
  f() // 3
  f() // 5
}
// 为函数实现接口

# 资源管理与错误处理

# defer

在一个函数中使用defer定义的语句将在函数结束前执行

多个defer语句将栈式存储所有语句,即defer列表后进先出

// 写入操作(再操作文件打开后应即使关闭)
func writeFile(filename string){
 file, err := os.Create(filename)
 if err != nil {
   panic(err)
 }
 defer file.Close()
 
 writer := bufio.NewWriter(file)
 defer writer.Flush()

 for i:= 0; i< 20; i++ {
   fmt.Fprintln(writer, i)
 }
}
                
func main() {
 writeFile('a.txt')
}

# 错误处理

最常见处理方式

// 常见操作
func writeFile(filename string) {
	file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)

	if err != nil {
		if pathError, ok := err.(*os.PathError); !ok {
			panic(err)
		} else {
			fmt.Printf("%s, %s, %s\n",
				pathError.Op,
				pathError.Path,
				pathError.Err,
			)
			return
		}
	}
	defer file.Close()

	writer := bufio.NewWriter(file)
	defer writer.Flush()
}

func main() {
	writeFile("a.txt")
}

那么如果更优雅的去处理错误呢?

对于一个文件列表的服务,可能是下面逻辑(创建errWrapper包裹函数去处理error)

func HandleFileList(writer http.ResponseWriter, request *http.Request){
    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
}

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.Warn("error: %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(HandleFileList))
  err := http.ListenAndServe(":8888", nil)
  if err != nil {
    panic(err)
  }
}

# panic与recover

panic停止当前的函数执行,一直向上返回执行每一层reder,如果没遇见recover,程序直接退出

recover仅在defer中跳用,他可以获取panic的值,如果无法处理,可以重新panic

func tryRecover(){
  defer func() {
    r := recover()
    if err, ok := r.(error); ok {
      rmt.Println("Error:", err)
    }else{
      panic(r)
    }
  }()
  panic(errors.New("this is an error"))
}
func main(){
  tryRecover()
}

当然,此处我们使用panic只是模拟错误发生,它代表了程序发生错误,我们也可以直接写一个错误

func tryRecover(){
  defer func() {
    r := recover()
    if err, ok := r.(error); ok {
      rmt.Println("Error:", err)
    }else{
      panic(r)
    }
  }()
  b := 0
  a = 1 / b
  fmt.Println(a)
}

# 并发编程

协程

  • 非强占式多任务处理,由协程主动交出控制权
  • 编译器/解释器/虚拟机层面的多任务
  • 多个协程可能在多个或者一个线程上进行
func main() {
	for i:=0; i<1000;i++{
		go func(i) {
      for {
        fmt.Printf(i)
      }
		}(i)
	}
  time.Sleep(time.Milliscond)
}
// 此处fmt的io操作会主动交出控制权,我们也可以手动交出
func main() {
  var a [10]int
	for i:=0; i<1000;i++{
		go func(i) {
      for {
        a[i]++
        runtime.Gosched()
      }
		}(i)
	}
  time.Sleep(time.Milliscond)
}

channel用法

func worker(id int, c chan int) {
	for n := range c {
		fmt.Printf("Worker %d received %c\n",
			id, n)
	}
}
// 写法等同于下
// func worker(id int, c chan int) {
// 	for {
// 		n, ok := <-c
// 		if !ok {
// 			break
// 		}
// 		fmt.Printf("Worker %d received %c\n",
// 			id, n)
// 	}
// }


func createWorker(id int) chan<- int { // chan<-表示返回的值只能用于传数据
	c := make(chan int)
	go worker(id, c)
	return c
}

func chanDemo() {
	var channels [10]chan<- int
	for i := 0; i < 10; i++ {
		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 bufferedChannel() {
	c := make(chan int, 3) // 3个单位的缓存区,传入3个以内数据时,即使没有接收者,也不会报错
	go worker(0, c)
	c <- 'a'
	c <- 'b'
	c <- 'c'
	c <- 'd'
	time.Sleep(time.Millisecond)
}

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() {
	fmt.Println("Channel as first-class citizen")
	chanDemo()
	fmt.Println("Buffered channel")
	bufferedChannel()
	fmt.Println("Channel close and range")
	channelClose()
}

任务等待(使用channel等待goroutine结束,不再使用time.Sleep去等待

func doWork(id int,
	w worker) {
	for n := range w.in {
		fmt.Printf("Worker %d received %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 doWork(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)
	}

	wg.Add(20) // 添加任务数
	for i, worker := range workers {
		worker.in <- 'a' + i
	}
	for i, worker := range workers {
		worker.in <- 'A' + i
	}

	wg.Wait() // 等待任务全部做完
}

func main() {
	chanDemo()
}

使用select进行调度(从c1和c2同时接收数据,只收取最返回的


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("Worker %d received %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) // 10s后向返回的channel传入一个值
	tick := time.Tick(time.Second)     // 定时,每隔指定时间向返回的channel传入一个值
	for {
		var activeWorker chan<- int
		var activeValue int
		if len(values) > 0 {
			activeWorker = worker
			activeValue = values[0]
		}

		select {
		case n := <-c1:
			values = append(values, n)
		case n := <-c2:
			values = append(values, n)
		case activeWorker <- activeValue:
			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
		}
	}
}

传统同步机制(锁 mutex,建议使用channel进行场景处理

type atomicInt struct {
	value int
	lock  sync.Mutex
}

func (a *atomicInt) increment() {
	fmt.Println("safe increment")
	func() {
		a.lock.Lock()
		defer a.lock.Unlock()

		a.value++
	}()
}

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())
}

并发模式,同时接收多个channel传的数据

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(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 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")
	m3 := msgGen("service3")
	m := fanIn(m1, m2, m3)
	for {
		fmt.Println(<-m)
	}
}

# 工程化

# 依赖管理

使用最新的go mod模式管理依赖

初始化:go mod init[modulName]

增加依赖:go get -u [path]

更新版本:go get [@v...] / go mod tidy

打包:go build

使用go build fileName打包一个文件

但是当使用go build ./...打包所有目录下所有文件的时候,该命令只会去检查编译能否通过,使用go install ./...去进行打包。打包的结果会放在GOPATH的bin目录下

# 目录结构

每个包只能有一个main函数

# 测试

go语言使用的是表格驱动测试

# web开发

//brief_intro/echo.go
package main
import (...)

func echo(wr http.ResponseWriter, r *http.Request) {
    msg, err := ioutil.ReadAll(r.Body)
    if err != nil {
        wr.Write([]byte("echo error"))
        return
    }

    writeLen, err := wr.Write(msg)
    if err != nil || writeLen != len(msg) {
        log.Println(err, "write len:", writeLen)
    }
}

func main() {
    http.HandleFunc("/", echo)
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal(err)
    }
}