第一阶段:Go 快速入门(下篇) 一、内置运算符 1.1 算数运算符  {width=”4.1686034558180225in” height=”2.2823097112860893in”}
 
1、算数运算符使用 
Go快速入门详细讲义-下篇.md
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package  mainimport  (    "fmt"  ) func  main ()   {    fmt.Println("10+3=" , 10 +3 )        fmt.Println("10-3=" , 10 -3 )        fmt.Println("10*3=" , 10 *3 )             fmt.Println("10/3=" , 10 /3 )            fmt.Println("10.0/3=" , 10.0 /3 )             fmt.Println("10%3=" , 10 %3 )          fmt.Println("-10%3=" , -10 %3 )         fmt.Println("10%-3=" , 10 %-3 )         fmt.Println("-10%-3=" , -10 %-3 )    } 
 
2、i++ 
1 2 3 4 5 6 7 8 9 package  mainimport  (    "fmt"  ) func  main ()   {    var  i int  = 1      i++     fmt.Println("i=" , i)   } 
 
1.2 关系运算符 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package  mainimport  (    "fmt"  ) func  main ()   {    var  n1 int  = 9      var  n2 int  = 8      fmt.Println(n1 == n2)        fmt.Println(n1 != n2)        fmt.Println(n1 > n2)         fmt.Println(n1 >= n2)        fmt.Println(n1 < n2)         fmt.Println(n1 <= n2)        flag := n1 > n2     fmt.Println("flag=" , flag)   } 
 
1.3 逻辑运算符  {width=”6.383173665791776in” height=”2.3970166229221346in”}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package  mainimport  (    "fmt"  ) func  main ()   {         var  age int  = 40      if  age > 30  && age < 50  {         fmt.Println("ok1" )     }     if  age > 30  && age < 40  {         fmt.Println("ok2" )     }          if  age > 30  || age < 50  {         fmt.Println("ok3" )     }     if  age > 30  || age < 40  {         fmt.Println("ok4" )     }          if  age > 30  {         fmt.Println("ok5" )     }     if  !(age > 30 ) {         fmt.Println("ok6" )     } } 
 
1.4 赋值运算符  {width=”6.16953302712161in” height=”4.93458552055993in”}
1 2 3 4 5 6 7 8 9 10 11 12 package  mainimport  (    "fmt"  ) func  main ()   {    d := 8  + 2 *8          fmt.Println(d)             x := 10      x += 5              fmt.Println("x += 5 的值:" , x)   } 
 
二、条件循环 2.1 if else(分支结构) 1、if 条件判断基本写法 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package  mainimport  (    "fmt"  ) func  main ()   {    score := 65      if  score >= 90  {         fmt.Println("A" )     } else  if  score > 75  {         fmt.Println("B" )     } else  {         fmt.Println("C" )     } } 
 
2、if 条件判断特殊写法 
if 条件判断还有一种特殊的写法,可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package  mainimport  "fmt" func  main ()   {         if  score := 56 ; score >= 90  {         fmt.Println("A" )     } else  if  score > 75  {         fmt.Println("B" )     }else  {         fmt.Println("C" )         fmt.Println(score)      }      } 
 
2.2 for(循环结构) 2.1 for循环 
1.1、普通for循环 
1 2 3 4 5 6 7 8 package  mainimport  "fmt" func  main ()   {         for  i := 0 ; i < 10 ; i++ {         fmt.Println(i)     } } 
 
1.2 外部定义 i 
1 2 3 4 5 6 7 8 9 package  mainimport  "fmt" func  main ()   {    i := 0      for  i < 10  {         fmt.Println(i)         i++     } } 
 
2、模拟while循环 
Go 语言中是没有 while 语句的,我们可以通过 for 代替 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 package  mainimport  "fmt" func  main ()   {    k := 1      for  {          if  k <= 10  {             fmt.Println("ok~~" , k)         } else  {             break             }         k++     } } 
 
3、for range(键值循环) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package  mainimport  "fmt" func  main ()   {    str := "abc上海"      for  index, val := range  str {         fmt.Printf("索引=%d, 值=%c \n" , index, val)     } } 
 
2.3 switch case 
使用 switch 语句可方便地对大量的值进行条件判断 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package  mainimport  "fmt" func  main ()   {    score := "B"      switch  score {         case  "A" :             fmt.Println("非常棒" )         case  "B" :             fmt.Println("优秀" )         case  "C" :             fmt.Println("及格" )         default :             fmt.Println("不及格" )     } } 
 
2.4 break、continue 
break跳出循环
continue跳出本次循环
 
1、break跳出单循环 
1 2 3 4 5 6 7 8 9 10 11 12 13 package  mainimport  "fmt" func  main ()   {    k := 1      for  {          if  k <= 10  {             fmt.Println("ok~~" , k)         } else  {             break             }         k++     } } 
 
2、continue(继续下次循环) 
1 2 3 4 5 6 7 8 9 10 package  mainfunc  main ()   {    for  i := 0 ; i < 10 ; i++ {         if  i%2  == 0  {             continue          }         println (i)     } }  
 
三、函数 3.1 函数基础 1、函数定义 
函数是组织好的、可重复使用的、用于执行指定任务的代码块。
本文介绍了 Go 语言中函数的相关内容。
 
1 2 3 4 5 6 7 8 9 10 11 12 package  mainimport  "fmt" func  main ()   {    ret := intSum(1 , 2 )     fmt.Println(ret)  } func  intSum (x, y int )   int  {    return  x + y } 
 
2、函数返回值 
Go 语言中通过 return 关键字向外输出返回值。 
函数多返回值,Go 语言中函数支持多返回值,函数如果有多个返回值时必须用()将所有返回值包裹 起来 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package  mainimport  "fmt" func  main ()   {    plus, sub := calc(4 , 5 )     fmt.Println(plus)      fmt.Println(sub)   } func  calc (x, y int )   (int , int ) {    sum := x + y     sub := x - y     return  sum, sub } 
 
3.2 函数变量作用域 1、全局变量 
1 2 3 4 5 6 7 8 9 10 package  mainimport  "fmt" var  num int64  = 10 func  main ()   {    fmt.Printf("num=%d\n" , num)  } 
 
2、局部变量 
局部变量是函数内部定义的变量, 函数内定义的变量无法在该函数外使用 
例如下面的示例代码 main 函数中无法使用 testLocalVar 函数中定义的变量 x 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package  mainimport  "fmt" func  main ()   {              test() } func  test ()   {    name := "zhangsan"      fmt.Println(name) } 
 
3、for 循环语句中定义的变量 
我们之前讲过的 for 循环语句中定义的变量,也是只在 for 语句块中生效 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 package  mainimport  "fmt" func  main ()   {    testLocalVar3() } func  testLocalVar3 ()   {    for  i := 0 ; i < 10 ; i++ {         fmt.Println(i)      }      } 
 
四、结构体 1.1 什么是结构体 
Go语言中没有”类”的概念,也不支持”类”的继承等面向对象的概念。
Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。
 
1.2 结构体定义 1、基本实例化(法1)  
只有当结构体实例化时,才会真正地分配内存,也就是必须实例化后才能使用结构体的字段。 
结构体本身也是一种类型,我们可以像声明内置类型一样使用 var 关键字声明结构体类型 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package  mainimport  "fmt" type  person struct  {    name string      city string      age  int  } func  main ()   {    var  p1 person     p1.name = "张三"      p1.city = "北京"      p1.age = 18      fmt.Printf("p1=%v\n" , p1)       fmt.Printf("p1=%#v\n" , p1)  } 
 
2、new实例化(法2)  
我们还可以通过使用 new 关键字对结构体进行实例化,得到的是结构体的地址 
从打印的结果中我们可以看出 p2 是一个结构体指针。 
注意:在 Golang 中支持对结构体指针直接使用.来访问结构体的成员。 
p2.name = "张三" 其实在底层是 (*p2).name = "张三" 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package  mainimport  "fmt" type  person struct  {    name string      city string      age  int  } func  main ()   {    var  p2 = new (person)     p2.name = "张三"      p2.age = 20      p2.city = "北京"      fmt.Printf("p2=%#v \n" , p2)  } 
 
3、键值对初始化(法3)  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package  mainimport  "fmt" type  person struct  {    name string      city string      age int  } func  main ()   {    p4 := person{         name: "zhangsan" ,         city: "北京" ,         age: 18 ,     }          fmt.Printf("p4=%#v\n" , p4) } 
 
1.3 结构体方法和接收者 1、结构体说明  
在 go 语言中,没有类的概念但是可以给类型(结构体,自定义类型)定义方法。 
所谓方法就是定义了接收者的函数。
Go语言中的方法(Method)是一种作用于特定类型变量的函数。 
这种特定类型变量叫做接收者(Receiver)。 
接收者的概念就类似于其他语言中的this或者 self。 
 
 
方法的定义格式如下: 
 
1 2 3 func  (接收者变量 接收者类型)   方法名(参数列表) (返回参数) {    函数体 } 
 
给结构体 Person 定义一个方法打印 Person 的信息 
 
2、结构体方法和接收者  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package  mainimport  "fmt" type  Person struct  {    name string      age  int8  } func  (p Person)   printInfo() {    fmt.Printf("姓名:%v 年龄:%v" , p.name, p.age)  } func  main ()   {    p1 := Person{         name: "小王子" ,         age:  25 ,     }     p1.printInfo()  } 
 
3、值类型和指针类型接收者  
实例1:给结构体 Person 定义一个方法打印 Person 的信息 
1、值类型的接收者
当方法作用于值类型接收者时,Go 语言会在代码运行时将接收者的值复制一份。 
在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收 者变量本身。 
 
 
2、指针类型的接收者
指针类型的接收者由一个结构体的指针组成 
由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效 的。 
这种方式就十分接近于其他语言中面向对象中的 this 或者 self。 
 
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package  mainimport  "fmt" type  Person struct  {    name string      age  int  } func  (p Person)   printInfo() {    fmt.Printf("姓名:%v 年龄:%v\n" , p.name, p.age)  } func  (p *Person)   setInfo(name string , age int ) {    p.name = name     p.age = age } func  main ()   {    p1 := Person{         name: "小王子" ,         age:  25 ,     }     p1.printInfo()      p1.setInfo("张三" , 20 )     p1.printInfo()  } 
 
1.4、一个结构体实现多个接口 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package  mainimport  "fmt" type  AInterface interface  {	GetInfo() string  } type  BInterface interface  {	SetInfo(string , int ) } type  People struct  {	Name string  	Age int  } func  (p People)   GetInfo() string  {	return  fmt.Sprintf("姓名:%v 年龄:%d" , p.Name, p.Age) } func  (p *People)   SetInfo(name string , age int ) {	p.Name = name 	p.Age = age } func  main ()   {	var  people = &People{ 		Name: "张三" , 		Age: 20 , 	} 	 	var  p1 AInterface = people 	var  p2 BInterface = people 	fmt.Println(p1.GetInfo()) 	p2.SetInfo("李四" , 30 )          	fmt.Println(p1.GetInfo())       } 
 
1.5、接口嵌套(继承) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package  mainimport  "fmt" type  SayInterface interface  {	say() } type  MoveInterface interface  {	move() } type  Animal interface  {	SayInterface 	MoveInterface } type  Cat struct  {	name string  } func  (c Cat)   say() {	fmt.Println("喵喵喵" ) } func  (c Cat)   move() {	fmt.Println("猫会动" ) } func  main ()   {	var  x Animal 	x = Cat{name: "花花" } 	x.move()     	x.say()      } 
 
五、面向对象 5.1 Golang接口的定义 1、Golang 中的接口 
在Go语言中接口(interface)是一种类型,一种抽象的类型。 
接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细 节。 
实现接口的条件
一个对象只要全部实现了接口中的方法,那么就实现了这个接口。 
换句话说,接口就是一个需要实现的方法列表。 
 
 
 
2、为什么要使用接口 
上面的代码中定义了猫和狗,然后它们都会叫,你会发现main函数中明显有重复的代码 
如果我们后续再加上猪、青蛙等动物的话,我们的代码还会一直重复下去 
那我们能不能把它们当成“能叫的动物”来处理呢? 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package  mainimport  (    "fmt"  ) type  Cat struct  {    Name string  } func  (c Cat)   Say() string  {     return  c.Name + ":喵喵喵"   } type  Dog struct  {    Name string  } func  (d Dog)   Say() string  {     return  d.Name + ": 汪汪汪"   } func  main ()   {    c := Cat{Name: "小白猫" }      fmt.Println(c.Say())     d := Dog{"阿黄" }     fmt.Println(d.Say())  } 
 
3、定义一个Usber接口 
定义一个 Usber 接口让 Phone 和 Camera 结构体实现这个接口 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package  mainimport  "fmt" type  Usber interface  {    start()     stop() } type  Phone struct  {    Name string  } func  (p Phone)   start() {    fmt.Println(p.Name, "启动" ) } func  (p Phone)   stop() {    fmt.Println(p.Name, "关机" ) } func  main ()   {    p := Phone{         Name: "华为手机" ,     }     var  p1 Usber        p1 = p            p1.start()     p1.stop() } 
 
4、go中类 
5.2 空接口 1、空接口说明 
golang中空接口也可以直接当做类型来使用,可以表示任意类型
 
Golang 中的接口可以不定义任何方法,没有定义任何方法的接口就是空接口。
 
空接口表示没有任何约束,因此任何类型变量都可以实现空接口。
 
空接口在实际项目中用的是非常多的,用空接口可以表示任意数据类型。
 
 
2、空接口作为函数的参数 
1 2 3 4 5 6 7 8 9 10 11 12 13 package  mainimport  "fmt" func  show (a interface {})   {    fmt.Printf("值:%v 类型:%T\n" , a, a) } func  main ()   {    show(20 )                           show("你好golang" )                 slice := []int {1 , 2 , 34 , 4 }     show(slice)                      } 
 
3、切片实现空接口 
1 2 3 4 5 6 7 package  mainimport  "fmt" func  main ()   {    var  slice = []interface {}{"张三" , 20 , true , 32.2 }     fmt.Println(slice)   } 
 
4、map 的值实现空接口 
1 2 3 4 5 6 7 8 9 10 11 12 package  mainimport  "fmt" func  main ()   {         var  studentInfo = make (map [string ]interface {})     studentInfo["name" ] = "张三"      studentInfo["age" ] = 18      studentInfo["married" ] = false      fmt.Println(studentInfo)      } 
 
5.3 类型断言 
一个接口的值(简称接口值)是由一个具体类型和具体类型的值两部分组成的。 
这两部分分别称为接口的动态类型和动态值。 
如果我们想要判断空接口中值的类型,那么这个时候就可以使用类型断言 
其语法格式:x.(T)
x : 表示类型为 interface{}的变量 
T : 表示断言 x 可能是的类型 
 
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 package  mainimport  "fmt" func  main ()   {    var  x interface {}     x = "Hello golnag"      v, ok := x.(string )     if  ok {         fmt.Println(v)     }else  {         fmt.Println("非字符串类型" )     } } 
 
5.4 值接收者和指针接收者 1、值接收者 
当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。 
在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变 量本身。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package  mainimport  "fmt" type  Usb interface  {    Start()     Stop() } type  Phone struct  {    Name string  } func  (p Phone)   Start() {    fmt.Println(p.Name, "开始工作" ) } func  (p Phone)   Stop() {    fmt.Println("phone 停止" ) } func  main ()   {    phone1 := Phone{                Name: "小米手机" ,     }     var  p1 Usb = phone1           p1.Start()     phone2 := &Phone{                 Name: "苹果手机" ,     }     var  p2 Usb = phone2           p2.Start()             } 
 
2、指针接收者 
指针类型的接收者由一个结构体的指针组成 
由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。 
这种方式就十分接近于其他语言中面向对象中的this 或者self 。 
例如我们为Person 添加一个SetAge 方法,来修改实例变量的年龄。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package  mainimport  "fmt" type  Usb interface  {    Start()     Stop() } type  Phone struct  {    Name string  } func  (p *Phone)   Start() {    fmt.Println(p.Name, "开始工作" ) } func  (p *Phone)   Stop() {    fmt.Println("phone 停止" ) } func  main ()   {              phone2 := &Phone{              Name: "苹果手机" ,     }     var  p2 Usb = phone2      p2.Start()      } 
 
3、值类型接收者使用时机 
1、需要修改接收者中的值 
2、接收者是拷贝代价比较大的大对象 
3、保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。 
 
六、并发编程 6.1 并发介绍 1、并发和并行 
1 2 A. 多线程程序在一个核的cpu上运行,就是并发。 B. 多线程程序在多个核的cpu上运行,就是并行。 
 
 {width=”6.16953302712161in” height=”2.9602318460192474in”}
 {width=”3.126453412073491in” height=”1.6674409448818897in”}
 协程和线程  
 
6.2 goroutine 1、多线程编程缺点 
2、gouroutine 
Go语言中的goroutine就是这样一种机制,goroutine的概念类似于线程,但 goroutine是由Go的运行时(runtime)调度和管理的。
 
Go程序会智能地将 goroutine 中的任务合理地分配给每个CPU。
 
Go语言之所以被称为现代化的编程语言,就是因为它在语言层面已经内置了调度和上下文切换的机制。
 
在Go语言编程中你不需要去自己写进程、线程、协程,你的技能包里只有一个技能–goroutine 当你需要让某个任务并发执行的时候,你只需要把这个任务包装成一个函数开启一个goroutine去执行这个函数就可以了,就是这么简单粗暴。
 
 
6.3 协程基本使用 1、启动一个协程 
主线程中每个100毫秒打印一次,总共打印2次 
另外开启一个协程,打印10次 
情况一:打印是交替,证明是并行的 
情况二:开启的协程打印两次,就退出了(因为主线程退出了) 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package  mainimport  (    "fmt"      "time"  ) func  test ()   {    for  i := 0 ; i < 10 ; i++ {         fmt.Println("test() 你好golang" )         time.Sleep(time.Millisecond * 100 )     } } func  main ()   {    go  test()      for  i := 0 ; i < 2 ; i++ {         fmt.Println("main() 你好golang" )         time.Sleep(time.Millisecond * 100 )     } } 
 
2、WaitGroup 
主线程退出后所有的协程无论有没有执行完毕都会退出 
所以我们在主进程中可以通过WaitGroup等待协程执行完毕
sync.WaitGroup内部维护着一个计数器,计数器的值可以增加和减少。 
例如当我们启动了N 个并发任务时,就将计数器值增加N。 
每个任务完成时通过调用Done()方法将计数器减1。 
通过调用Wait()来等待并发任务执行完,当计数器值为0时,表示所有并发任务已经完成。 
 
 
 
1 2 3 4 var wg sync.WaitGroup       // 第一步:定义一个计数器 wg.Add(1)               // 第二步:开启一个协程计数器+1 wg.Done()               // 第三步:协程执行完毕,计数器-1 wg.Wait()               // 第四步:计数器为0时推出 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package  mainimport  (    "fmt"      "sync"      "time"  ) var  wg sync.WaitGroup           func  test1 ()   {    for  i := 0 ; i < 10 ; i++ {         fmt.Println("test1() 你好golang-" , i)         time.Sleep(time.Millisecond * 100 )     }     wg.Done()  } func  test2 ()   {    for  i := 0 ; i < 2 ; i++ {         fmt.Println("test2() 你好golang-" , i)         time.Sleep(time.Millisecond * 100 )     }     wg.Done()  } func  main ()   {    wg.Add(1 )       go  test1()      wg.Add(1 )       go  test2()      wg.Wait()      fmt.Println("主线程退出..." ) } 
 
3、开启多个协程 
在 Go 语言中实现并发就是这样简单,我们还可以启动多个 goroutine。 
这里使用了 sync.WaitGroup 来实现等待 goroutine 执行完毕 
多次执行上面的代码,会发现每次打印的数字的顺序都不一致。 
这是因为 10 个 goroutine是并发执行的,而 goroutine 的调度是随机的。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package  mainimport  (    "fmt"      "sync"  ) var  wg sync.WaitGroupfunc  hello (i int )   {    defer  wg.Done()      fmt.Println("Hello Goroutine!" , i) } func  main ()   {    for  i := 0 ; i < 10 ; i++ {         wg.Add(1 )          go  hello(i)     }     wg.Wait()  } 
 
6.4.Channel 1.Channel 管道 1.1 Channel说明 
共享内存交互数据弊端
单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。 
虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题。 
为了保证数据交换的正确性,必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。 
 
 
channel好处
Go 语言中的通道(channel)是一种特殊的类型。 
通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。 
每一个通道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。 
如果说goroutine是Go程序并发的执行体,channel就是它们之间的连接。 
channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。 
 
 
 
1.2 channel类型 
channel 是一种类型,一种引用类型 
声明管道类型的格式如下: 
 
1 2 3 4 var  变量 chan  元素类型var  ch1 chan  int      var  ch2 chan  bool     var  ch3 chan  []int     
 
1.3 创建channel 
声明的管道后需要使用 make 函数初始化之后才能使用。 
创建 channel  的格式如下:make(chan 元素类型, 容量) 
 
1 2 3 4 5 6 ch1 := make (chan  int , 10 ) ch2 := make (chan  bool , 4 ) ch3 := make (chan  []int , 3 ) 
 
2.channel操作 
管道有发送(send)、接收(receive)和关闭(close)三种操作。 
发送和接收都使用<-符号。 
现在我们先使用以下语句定义一个管道: 
 
 
2.1 发送(将数据放在管道内) 
 
2.2 接收(从管道内取值) 
 
2.3 关闭管道 
我们通过调用内置的 close 函数来关闭管道: close(ch) 
关于关闭管道需要注意的事情是,只有在通知接收方 goroutine 所有的数据都发送完毕的时候才需要关闭管道。 
管道是可以被垃圾回收机制回收的,它和关闭文件是不一样的,在结束操作之后关闭文件是必须要做的,但关闭管道不是必须的。 
关闭后的管道有以下特点:
对一个关闭的管道再发送值就会导致 panic。 
对一个关闭的管道进行接收会一直获取值直到管道为空 
对一个关闭的并且没有值的管道执行接收操作会得到对应类型的零值。 
关闭一个已经关闭的管道会导致 panic。 
 
 
 
2.4 管道阻塞 2.4.1 无缓冲的管道 
如果创建管道的时候没有指定容量,那么我们可以叫这个管道为无缓冲的管道 
无缓冲的管道又称为阻塞的管道。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package  mainimport  "fmt" func  main ()   {	ch := make (chan  int ) 	ch <- 10  	fmt.Println("发送成功" ) } 
 
2.4.2 有缓冲的管道 
解决上面问题的方法还有一种就是使用有缓冲区的管道。 
我们可以在使用 make 函数初始化管道的时候为其指定管道的容量 
只要管道的容量大于零,那么该管道就是有缓冲的管道,管道的容量表示管道中能存放元素的数量。 
就像你小区的快递柜只有那么个多格子,格子满了就装不下了,就阻塞了,等到别人取走一个快递员就能往里面放一个。 
 
1 2 3 4 5 6 7 8 9 package  mainimport  "fmt" func  main ()   {	ch := make (chan  int , 5 ) 	ch <- 10  	ch <- 12  	fmt.Println("发送成功" ) } 
 
3.从channel取值 3.1 优雅的从channel取值 
当通过通道发送有限的数据时,我们可以通过close函数关闭通道来告知从该通道接收值的goroutine停止等待。 
当通道被关闭时,往该通道发送值会引发panic,从该通道里接收的值一直都是类型零值。 
那如何判断一个通道是否被关闭了呢? 
for range的方式判断通道关闭 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package  mainimport  (	"fmt"  ) func  f1 (ch1 chan  int )   {	for  i := 0 ; i < 100 ; i++ { 		ch1 <- i 	} 	close (ch1) } func  f2 (ch1 chan  int , ch2 chan  int )   {	for  { 		i, ok := <-ch1  		if  !ok { 			break  		} 		ch2 <- i * i 	} 	close (ch2) } func  main ()   {	ch1 := make (chan  int ) 	ch2 := make (chan  int ) 	 	go  f1(ch1) 	 	go  f2(ch1, ch2) 	 	for  i := range  ch2 {  		fmt.Println(i) 	} } 
 
3.2 Goroutine结合Channel管道 
需求 1 :定义两个方法,一个方法给管道里面写数据,一个给管道里面读取数据,要求同步进行。 
1、开启一个 fn1 的的协程给向管道 inChan 中写入 100 条数据 
2、开启一个 fn2 的协程读取 inChan 中写入的数据 
3、注意:fn1 和 fn2 同时操作一个管道 
4、主线程必须等待操作完成后才可以退出 
注:for range的方式判断通道关闭,推出程序 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package  mainimport  (	"fmt"  	"sync"  	"time"  ) var  wg sync.WaitGroupfunc  main ()    {	intChan := make (chan  int ,10 ) 	wg.Add(2 ) 	go  write(intChan) 	go  read(intChan) 	wg.Wait() 	fmt.Println("读取完毕..." ) } func  write (intChan chan  int )    {	defer  wg.Done() 	for  i:=0 ;i<10 ;i++{ 		intChan <- i 	} 	close (intChan) } func  read (intChan chan  int )    {	defer  wg.Done() 	for  v := range  intChan { 		fmt.Println(v) 		time.Sleep(time.Second) 	} } 
 
4.单向管道 
有的时候我们会将管道作为参数在多个任务函数间传递 
很多时候我们在不同的任务函数中使用管道都会对其进行限制 
比如限制管道在函数中只能发送或只能接收 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package  mainimport  (	"fmt"  ) func  main ()   {	 	 	 	var  chan2 chan <- int  	chan2 = make (chan  int , 3 ) 	chan2<- 20  	 	fmt.Println("chan2=" , chan2) 	 	 	var  chan3 <-chan  int  	num2 := <-chan3 	 	fmt.Println("num2" , num2) } 
 
5.Goroutine池 
本质上是生产者消费者模型 
在工作中我们通常会使用可以指定启动的goroutine数量–worker pool模式,控制goroutine的数量,防止goroutine泄漏和暴涨。 
一个简易的work pool示例代码如下: 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package  mainimport  (	"fmt"  	"time"  ) func  worker (id int , jobs <-chan  int , results chan <- int )   {	for  j := range  jobs { 		fmt.Printf("worker:%d start job:%d\n" , id, j) 		time.Sleep(time.Second) 		fmt.Printf("worker:%d end job:%d\n" , id, j) 		results <- j * 2  	} } func  main ()   {	jobs := make (chan  int , 100 ) 	results := make (chan  int , 100 ) 	 	for  w := 1 ; w <= 3 ; w++ { 		go  worker(w, jobs, results) 	} 	 	for  j := 1 ; j <= 5 ; j++ { 		jobs <- j 	} 	close (jobs) 	 	for  a := 1 ; a <= 5 ; a++ { 		<-results 	} } 
 
6.5.select select 多路复用
6.1 select说明 
传统的方法在遍历管道时,如果不关闭会阻塞而导致 deadlock,在实际开发中,可能我们不好确定什么关闭该管道。 
这种方式虽然可以实现从多个管道接收值的需求,但是运行性能会差很多。 
为了应对这种场景,Go 内置了 select 关键字,可以同时响应多个管道的操作。 
select 的使用类似于 switch 语句,它有一系列 case 分支和一个默认的分支。 
每个 case 会对应一个管道的通信(接收或发送)过程。 
select 会一直等待,直到某个 case 的通信操作完成时,就会执行 case 分支对应的语句。 
具体格式如下: 
 
1 2 3 4 5 6 7 8 select  {case  <-chan1:    case  chan2 <- 1 :    default :    } 
 
6.2 select 的使用 
使用 select 语句能提高代码的可读性。 
可处理一个或多个 channel 的发送/接收操作。 
如果多个 case 同时满足,select 会随机选择一个。 
对于没有 case 的 select{}会一直等待,可用于阻塞 main 函数。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package  mainimport  (	"fmt"  	"time"  ) func  main ()   {	 	 	intChan := make (chan  int , 10 ) 	for  i := 0 ; i < 10 ; i++ { 		intChan <- i 	} 	 	stringChan := make (chan  string , 5 ) 	for  i := 0 ; i < 5 ; i++ { 		stringChan <- "hello"  + fmt.Sprintf("%d" , i) 	} 	 	for  { 		select  { 		case  v := <-intChan: 			fmt.Printf("从 intChan 读取的数据%d\n" , v) 			time.Sleep(time.Millisecond * 50 ) 		case  v := <-stringChan: 			fmt.Printf("从 stringChan 读取的数据%v\n" , v) 			time.Sleep(time.Millisecond * 50 ) 		default : 			fmt.Printf("数据获取完毕" ) 			return   		} 	} } 
 
6.6.并发安全和锁 1.并发安全与锁 1.1 并发安全 
有时候在Go代码中可能会存在多个goroutine同时操作一个资源(临界区),这种情况会发生竞态问题(数据竞态)。 
类比现实生活中的例子有十字路口被各个方向的的汽车竞争;还有火车上的卫生间被车厢里的人竞争。 
下面开启两个协程,对变量x加一操作,分别加5000次,理想结果是10000,实际三次结果都不相同 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package  mainimport  (	"fmt"  	"sync"  ) var  x int64 var  wg sync.WaitGroupfunc  add ()   {	for  i := 0 ; i < 5000 ; i++ { 		x = x + 1  	} 	wg.Done() } func  main ()   {	wg.Add(2 ) 	go  add() 	go  add() 	wg.Wait() 	fmt.Println(x) } 
 
1.2 互斥锁 
互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个goroutine可以访问共享资源。 
Go语言中使用sync包的Mutex类型来实现互斥锁。 
使用互斥锁来修复上面代码的问题: 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package  mainimport  (	"fmt"  	"sync"  ) var  x int64 var  wg sync.WaitGroupvar  lock sync.Mutexfunc  add ()   {	for  i := 0 ; i < 5000 ; i++ { 		lock.Lock()  		x = x + 1  		lock.Unlock()  	} 	wg.Done() } func  main ()   {	wg.Add(2 ) 	go  add() 	go  add() 	wg.Wait() 	fmt.Println(x)    } 
 
1.3 读写互斥锁 
互斥锁是完全互斥的,但是有很多实际的场景下是读多写少的 
当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的,这种场景下使用读写锁是更好的一种选择。 
读写锁在Go语言中使用sync包中的RWMutex类型。 
读写锁分为两种:读锁和写锁
当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待; 
当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。 
 
 
注意:是读写锁非常适合读多写少的场景,如果读和写的操作差别不大,读写锁的优势就发挥不出来。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package  mainimport  (	"fmt"  	"sync"  	"time"  ) var  wg sync.WaitGroupvar  mutex sync.RWMutexfunc  write ()   {	mutex.Lock() 	fmt.Println("执行写操作" ) 	time.Sleep(time.Second * 2 ) 	mutex.Unlock() 	wg.Done() } func  read ()   {	mutex.RLock() 	fmt.Println("---执行读操作" ) 	time.Sleep(time.Second * 2 ) 	mutex.RUnlock() 	wg.Done() } func  main ()   {	for  i := 0 ; i < 10 ; i++ {	 		wg.Add(1 ) 		go  write() 	} 	for  i := 0 ; i < 10 ; i++ {	 		wg.Add(1 ) 		go  read() 	} 	wg.Wait() } 
 
2.sync其他方法 2.1 sync.WaitGroup 
在代码中生硬的使用time.Sleep肯定是不合适的,Go语言中可以使用sync.WaitGroup来实现并发任务的同步。 
sync.WaitGroup有以下几个方法: 
 
方法名 
功能 
 
 
(wg * WaitGroup) Add(delta int) 
计数器+delta 
 
(wg *WaitGroup) Done() 
计数器-1 
 
(wg *WaitGroup) Wait() 
阻塞直到计数器变为0 
 
sync.WaitGroup内部维护着一个计数器,计数器的值可以增加和减少。 
例如当我们启动了N 个并发任务时,就将计数器值增加N。 
每个任务完成时通过调用Done()方法将计数器减1。 
通过调用Wait()来等待并发任务执行完,当计数器值为0时,表示所有并发任务已经完成。 
我们利用sync.WaitGroup将上面的代码优化一下: 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package  mainimport  (	"fmt"  	"sync"  ) var  wg sync.WaitGroupfunc  hello ()   {	defer  wg.Done() 	fmt.Println("Hello Goroutine!" ) } func  main ()   {	wg.Add(1 ) 	go  hello()  	fmt.Println("main goroutine done!" ) 	wg.Wait() } 
 
2.2sync.Once 
在编程的很多场景下我们需要确保某些操作在高并发的场景下只执行一次,例如只加载一次配置文件、只关闭一次通道等。 
Go语言中的sync包中提供了一个针对只执行一次场景的解决方案–sync.Once。 
sync.Once只有一个Do方法,其签名如下: 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var  icons map [string ]image.Imagevar  loadIconsOnce sync.Oncefunc  loadIcons ()   {	icons = map [string ]image.Image{ 		"left" :  loadIcon("left.png" ), 		"up" :    loadIcon("up.png" ), 		"right" : loadIcon("right.png" ), 		"down" :  loadIcon("down.png" ), 	} } func  Icon (name string )   image.Image {	loadIconsOnce.Do(loadIcons) 	return  icons[name] } 
 
2.3 sync.Map 
Go语言中内置的map不是并发安全的。请看下面的示例 
下面的代码开启少量几个goroutine的时候可能没什么问题 
当并发多了之后执行上面的代码就会报fatal error: concurrent map writes错误。 
原因:
因为 map 变量为 指针类型变量,并发写时,多个协程同时操作一个内存 
类似于多线程操作同一个资源会发生竞争关系,共享资源会遭到破坏 
因此golang 出于安全的考虑,抛出致命错误:fatal error: concurrent map writes。 
 
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package  mainimport  (	"fmt"  	"strconv"  	"sync"  ) var  m = make (map [string ]int )func  get (key string )   int  {	return  m[key] } func  set (key string , value int )   {	m[key] = value } func  main ()   {	wg := sync.WaitGroup{} 	for  i := 0 ; i < 20 ; i++ { 		wg.Add(1 ) 		go  func (n int )   { 			key := strconv.Itoa(n) 			set(key, n) 			fmt.Printf("k=:%v,v:=%v\n" , key, get(key)) 			wg.Done() 		}(i) 	} 	wg.Wait() } 
 
像这种场景下就需要为map加锁来保证并发的安全性了,Go语言的sync包中提供了一个开箱即用的并发安全版map–sync.Map。 
开箱即用表示不用像内置的map一样使用make函数初始化就能直接使用。 
同时sync.Map内置了诸如Store、Load、LoadOrStore、Delete、Range等操作方法。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package  mainimport  (	"fmt"  	"strconv"  	"sync"  ) var  m = sync.Map{}func  main ()   {	wg := sync.WaitGroup{} 	for  i := 0 ; i < 20 ; i++ { 		wg.Add(1 ) 		go  func (n int )   { 			key := strconv.Itoa(n) 			m.Store(key, n) 			value, _ := m.Load(key) 			fmt.Printf("k=:%v,v:=%v\n" , key, value) 			wg.Done() 		}(i) 	} 	wg.Wait() } 
 
6.7.goroutine原理 1.设计思路 
1.1 设计描述 
启动服务之时先初始化一个 Goroutine Pool 池,这个 Pool 维护了一个类似栈的 LIFO 队列 ,里面存放负责处理任务的 Worker 
然后在 client 端提交 task 到 Pool 中之后,在 Pool 内部,接收 task 之后的核心操作是
检查当前 Worker 队列中是否有可用的 Worker,如果有,取出执行当前的 task; 
没有可用的 Worker,判断当前在运行的 Worker 是否已超过该 Pool 的容量 
每个 Worker 执行完任务之后,放回 Pool 的队列中等待 
 
 
 
1.2 Pool struct 1 2 3 4 5 6 7 8 9 10 11 12 13 14 type  sig struct {}type  f func ()   error type  Pool struct  {	capacity int32                     	running int32                      	expiryDuration time.Duration      	workers []*Worker                 	release chan  sig                  	lock sync.Mutex                   	once sync.Once                    } 
 
1.3 初始化 Pool 并启动定期清理过期 worker 任务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func  NewPool (size int )   (*Pool, error ) {	return  NewTimingPool(size, DefaultCleanIntervalTime) } func  NewTimingPool (size, expiry int )   (*Pool, error ) {	if  size <= 0  { 		return  nil , ErrInvalidPoolSize 	} 	if  expiry <= 0  { 		return  nil , ErrInvalidPoolExpiry 	} 	p := &Pool{ 		capacity:       int32 (size), 		freeSignal:     make (chan  sig, math.MaxInt32), 		release:        make (chan  sig, 1 ), 		expiryDuration: time.Duration(expiry) * time.Second, 	} 	 	p.monitorAndClear() 	return  p, nil  } 
 
1.4 提交任务到 Pool 
第一个 if 判断当前 Pool 是否已被关闭,若是则不再接受新任务,否则获取一个 Pool 中可用的 worker,绑定该 task 执行。 
 
1 2 3 4 5 6 7 8 9 func  (p *Pool)   Submit(task f) error  {	if  len (p.release) > 0  { 		return  ErrPoolClosed 	} 	w := p.getWorker() 	w.task <- task 	return  nil  } 
 
1.5 获取可用 worker(核心) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 func  (p *Pool)   getWorker() *Worker {	var  w *Worker 	waiting := false 	 	p.lock.Lock()	    	idleWorkers := p.workers 	n := len (idleWorkers) - 1  	if  n < 0  {	  		 		waiting = p.Running() >= p.Cap() 	} else  {		 		w = idleWorkers[n] 		idleWorkers[n] = nil  		p.workers = idleWorkers[:n] 	} 	p.lock.Unlock()	   	if  waiting {	  		for  {		  			p.lock.Lock() 			idleWorkers = p.workers 			l := len (idleWorkers) - 1  			if  l < 0  { 				p.lock.Unlock() 				continue  			} 			w = idleWorkers[l] 			idleWorkers[l] = nil  			p.workers = idleWorkers[:l] 			p.lock.Unlock() 			break  		} 		 	} else  if  w == nil  { 		w = &Worker{ 			pool: p, 			task: make (chan  f, 1 ), 		} 		w.run() 		 		p.incRunning() 	} 	return  w } 
 
1.6 执行任务 
结合前面的 p.Submit(task f) 和 p.getWorker() ,提交任务到 Pool 之后,获取一个可用 worker 
每新建一个 worker 实例之时都需要调用 w.run() 启动一个 goroutine 监听 worker 的任务列表 task ,一有任务提交进来就执行; 
所以,当调用 worker 的 sendTask(task f) 方法提交任务到 worker 的任务队列之后,马上就可以被接收并执行 
当任务执行完之后,会调用 w.pool.putWorker(w *Worker) 方法将这个已经执行完任务的 worker 从当前任务解绑放回 Pool 中,以供下个任务可以使用 
至此,一个任务从提交到完成的过程就此结束,Pool 调度将进入下一个循环。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 type  Worker struct  {	pool *Pool    	task chan  f   	recycleTime time.Time	  } func  (w *Worker)   run() {	go  func ()   { 		 		for  f := range  w.task { 			if  f == nil  { 				 				w.pool.decRunning() 				return  			} 			f() 			 			w.pool.putWorker(w) 		} 	}() } 
 
1.7 worker回收(goroutine 复用) 1 2 3 4 5 6 7 8 func  (p *Pool)   putWorker(worker *Worker) {	 	worker.recycleTime = time.Now() 	p.lock.Lock() 	p.workers = append (p.workers, worker) 	p.lock.Unlock() } 
 
1.8 动态扩容或者缩小池容量 1 2 3 4 5 6 7 8 9 10 11 12 13 func  (p *Pool)   ReSize(size int ) {	if  size == p.Cap() { 		return  	} 	atomic.StoreInt32(&p.capacity, int32 (size)) 	diff := p.Running() - size 	if  diff > 0  { 		for  i := 0 ; i < diff; i++ { 			p.getWorker().task <- nil  		} 	} } 
 
1.9 定期清理过期 Worker 
定期检查空闲 worker 队列中是否有已过期的 worker 并清理 
因为采用了 LIFO 后进先出队列存放空闲 worker,所以该队列默认已经是按照 worker 的最后运行时间由远及近排序 
可以方便地按顺序取出空闲队列中的每个 worker 并判断它们的最后运行时间与当前时间之差是否超过设置的过期时长 
若是,则清理掉该 goroutine,释放该 worker,并且将剩下的未过期 worker 重新分配到当前 Pool 的空闲 worker 队列中,进一步节省系统资源 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func  (p *Pool)   periodicallyPurge() {	heartbeat := time.NewTicker(p.expiryDuration) 	for  range  heartbeat.C { 		currentTime := time.Now() 		p.lock.Lock() 		idleWorkers := p.workers 		if  len (idleWorkers) == 0  && p.Running() == 0  && len (p.release) > 0  { 			p.lock.Unlock() 			return  		} 		n := 0  		for  i, w := range  idleWorkers { 			if  currentTime.Sub(w.recycleTime) <= p.expiryDuration { 				break  			} 			n = i 			w.task <- nil  			idleWorkers[i] = nil  		} 		n++ 		if  n >= len (idleWorkers) { 			p.workers = idleWorkers[:0 ] 		} else  { 			p.workers = idleWorkers[n:] 		} 		p.lock.Unlock() 	} } 
 
2.pool使用 2.1 公共池 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package  mainimport  (	"fmt"  	"sync"  	"time"  	"github.com/panjf2000/ants/v2"  ) func  demoFunc ()   {	time.Sleep(10  * time.Millisecond) 	fmt.Println("Hello World!" ) } func  main ()   {	 	defer  ants.Release() 	var  wg sync.WaitGroup 	syncCalculateSum := func ()   { 		demoFunc() 		wg.Done() 	} 	for  i := 0 ; i < 1000 ; i++ { 		wg.Add(1 ) 		_ = ants.Submit(syncCalculateSum) 	} 	wg.Wait() 	fmt.Printf("running goroutines: %d\n" , ants.Running()) 	fmt.Printf("finish all tasks.\n" ) } 
 
2.2 方法绑定池 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package  mainimport  (	"fmt"  	"github.com/panjf2000/ants/v2"  	"sync"  ) func  myFunc (i interface {})   {	fmt.Printf("run with %d\n" , i) } func  main ()   {	defer  ants.Release() 	var  wg sync.WaitGroup 	 	p, _ := ants.NewPoolWithFunc(10 , func (i interface {})   { 		myFunc(i) 		wg.Done() 	}) 	defer  p.Release() 	 	for  i := 0 ; i < 1000 ; i++ { 		wg.Add(1 ) 		_ = p.Invoke(int32 (i)) 	} 	wg.Wait() 	fmt.Printf("running goroutines: %d\n" , p.Running()) } 
 
七、常用模块 一、fmt 1.1 常用占位符 
动词 
功能 
 
 
%v 
按值的本来值输出 
 
%+v 
在 %v 的基础上,对结构体字段名和值进行展开 
 
%#v 
输出 Go 语言语法格式的值 
 
%T 
输出 Go 语言语法格式的类型和值 
 
%% 
输出 %% 本体 
 
%b 
整型以二进制方式显示 
 
%o 
整型以八进制方式显示 
 
%d 
整型以十进制方式显示 
 
%x 
整型以 十六进制显示 
 
%X 
整型以十六进制、字母大写方式显示 
 
%U 
Unicode 字符 
 
%f 
浮点数 
 
%p 
指针,十六进制方式显示 
 
1.2 Print 
Println:
一次输入多个值的时候 Println 中间有空格 
Println 会自动换行,Print 不会 
 
 
Print:
一次输入多个值的时候 Print 没有 中间有空格  
Print 不会自动换行 
 
 
Printf 
是格式化输出,在很多场景下比 Println 更方便 
 
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 package  mainimport  "fmt" func  main ()   {    fmt.Print("zhangsan" , "lisi" , "wangwu" )        fmt.Println("zhangsan" , "lisi" , "wangwu" )      name := "zhangsan"      age := 20      fmt.Printf("%s 今年 %d 岁\n" , name, age)          fmt.Printf("值:%v --> 类型: %T" , name, name)  } 
 
1.3 Sprint 
Sprint系列函数会把传入的数据生成并返回一个字符串。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 package  mainimport  "fmt" func  main ()   {    s1 := fmt.Sprint("枯藤" )     fmt.Println(s1)      name := "枯藤"      age := 18      s2 := fmt.Sprintf("name:%s,age:%d" , name, age)      fmt.Println(s2)     s3 := fmt.Sprintln("枯藤" )      fmt.Println(s3) } 
 
二、time 2.1 时间类型 
我们可以通过 time.Now()函数获取当前的时间对象,然后获取时间对象的年月日时分秒等信息。 
注意:%02d 中的 2 表示宽度,如果整数不够 2 列就补上 0 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package  mainimport  (    "fmt"      "time"  ) func  main ()   {    now := time.Now()        fmt.Printf("current time:%v\n" , now)          year := now.Year()          month := now.Month()        day := now.Day()            hour := now.Hour()             minute := now.Minute()      
 
2.2 时间戳 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package  mainimport  (    "fmt"      "time"  ) func  main ()   {    now := time.Now()                 timestamp1 := now.Unix()          timestamp2 := now.UnixNano()      fmt.Printf("current timestamp1:%v\n" , timestamp1)   timestamp1:1623560753      fmt.Printf("current timestamp2:%v\n" , timestamp2)   timestamp2:1623560753965606600  } 
 
使用time.Unix() 函数可以将时间戳转为时间格式 
 
1 2 3 4 5 6 7 8 9 10 11 12 func  timestampDemo2 (timestamp int64 )   {    timeObj := time.Unix(timestamp, 0 )      fmt.Println(timeObj)     year := timeObj.Year()          month := timeObj.Month()        day := timeObj.Day()            hour := timeObj.Hour()          minute := timeObj.Minute()       second := timeObj.Second()       fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n" , year, month, day, hour, minute,  second) } 
 
2.3 时间间隔 
time.Duration 是time 包定义的一个类型,它代表两个时间点之间经过的时间,以纳秒为单 位。 
time.Duration 表示一段时间间隔,可表示的最长时间段大约290年。 
time包中定义的时间间隔类型的常量如下: 
 
1 2 3 4 5 6 7 8 const  (    Nanosecond  Duration = 1      Microsecond          = 1000  * Nanosecond     Millisecond          = 1000  * Microsecond     Second               = 1000  * Millisecond     Minute               = 60  * Second     Hour                 = 60  * Minute ) 
 
2.4 时间格式化 
时间类型有一个自带的方法Format 进行格式化 
需要注意的是Go语言中格式化时间模板不是常见的Y-m-d H:M:S 
而是使用Go的诞生时间2006年1月2号15点04分(记忆口诀为2006 1 2 3 4)。 
补充:如果想格式化为12小时方式,需指定PM 。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 package  mainimport  (    "fmt"      "time"  ) func  main ()   {    now := time.Now()          fmt.Println(now.Format("2006-01-02 15:04:05.000 Mon Jan" ))  13 :10 :18.143  Sun Jun} 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package  mainimport  (    "fmt"      "time"  ) func  main ()   {    loc, _ := time.LoadLocation("Asia/Shanghai" )          timeObj, err := time.ParseInLocation("2006-01-02 15:04:05" , "2019-08-04 14:15:20" ,  loc)     if  err != nil  {         fmt.Println(err)         return      }     fmt.Println(timeObj)  } 
 
2.5 时间操作函数 
Add
我们在日常的编码过程中可能会遇到要求时间+时间间隔的需求 
Go 语言的时间对象有提供Add 方法如下 
 
 
Sub
 
 
1 2 3 4 5 6 7 package  mainimport  (    "fmt"      "time"  ) 
 
三、encoding-json包 3.1 struct与json 
比如我们 Golang 要给 App 或者小程序提供 Api 接口数据,这个时候就需要涉及到结构体和Json 之间的相互转换 
GolangJSON  序列化是指把结构体数据转化成 JSON 格式的字符串 
Golang  JSON  的反序列化是指把 JSON 数据转化成 Golang 中的结构体对象 
Golang 中 的 序 列 化 和 反 序 列 化 主 要 通 过 "encoding/json" 包 中 的 json.Marshal() 和 json.Unmarshal()方法实现 
 
1、struct转Json字符串 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package  mainimport  (    "encoding/json"      "fmt"  ) type  Student struct  {    ID int      Gender string      name string           Sno string  } func  main ()   {    var  s1 = Student{         ID: 1 ,         Gender: "男" ,         name: "李四" ,         Sno: "s0001" ,     }     fmt.Printf("%#v\n" , s1)   Sno:"s0001" }     var  s, _ = json.Marshal(s1)     jsonStr := string (s)     fmt.Println(jsonStr)    } 
 
2、Json字符串转struct 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package  mainimport  (    "encoding/json"      "fmt"  ) type  Student struct  {    ID int      Gender string      Name string      Sno string  } func  main ()   {    var  jsonStr = `{"ID":1,"Gender":"男","Name":"李四","Sno":"s0001"}`      var  student Student        err := json.Unmarshal([]byte (jsonStr), &student)     if  err != nil  {         fmt.Printf("unmarshal err=%v\n" , err)     }      student.Name=李四      fmt.Printf("反序列化后 student=%#v student.Name=%v \n" , student, student.Name) } 
 
3.2 struct tag 1、Tag标签说明 
Tag 是结构体的元信息,可以在运行的时候通过反射的机制读取出来。 
Tag 在结构体字段的后方定义,由一对反引号包裹起来 
具体的格式如下: 
 
1 key1:"value1"  key2:"value2"  
 
结构体 tag 由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。 
同一个结构体字段可以设置多个键值对 tag,不同的键值对之间使用空格分隔。 
注意事项: 
为结构体编写 Tag 时,必须严格遵守键值对的规则。 
结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误, 通过反射也无法正确取值。 
例如不要在 key 和 value 之间添加空格。 
 
 
 
2、Tag结构体转化Json字符串 1 2 3 4 5 6 7 8 9 10 11 package  mainimport  (    "encoding/json"      "fmt"  ) type  Student struct  {    ID int  `json:"id"`             Gender string  `json:"gender"`      Name string      Sno string  
 
3、Json字符串转成Tag结构体 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package  mainimport  (    "encoding/json"      "fmt"  ) type  Student struct  {    ID int  `json:"id"`       Gender string  `json:"gender"`      Name string      Sno string  } func  main ()   {    var  s2 Student     var  str = `{"id":1,"gender":"男","Name":"李四","Sno":"s0001"}`      err := json.Unmarshal([]byte (str), &s2)     if  err != nil  {         fmt.Println(err)     }          fmt.Printf("%#v" , s2) } 
 
四、Flag 4.1 Flag 
Go语言内置的flag包实现了命令行参数的解析,flag包使得开发命令行工具更为简单。 
 
flag.Parse() 通过以上两种方法定义好命令行flag参数后,需要通过调用flag.Parse()来对命令行参数进行解析。
支持的命令行参数格式有以下几种:
-flag xxx (使用空格,一个-符号)
 
–flag xxx (使用空格,两个-符号)
 
-flag=xxx (使用等号,一个-符号)
 
–flag=xxx (使用等号,两个-符号)
 
 
其中,布尔类型的参数必须使用等号的方式指定。
Flag解析在第一个非flag参数(单个”-“不是flag参数)之前停止,或者在终止符”–”之后停止。
2、其他函数 
flag.Args() ////返回命令行参数后的其他参数,以[]string类型 
 
flag.NArg() //返回命令行参数后的其他参数个数
 
flag.NFlag() //返回使用的命令行参数个数
 
 
4.2 完整示例 1、main.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package  mainimport  (    "flag"      "fmt"      "time"  ) func  main ()   {         var  name string      var  age int      var  married bool      var  delay time.Duration     flag.StringVar(&name, "name" , "张三" , "姓名" )     flag.IntVar(&age, "age" , 18 , "年龄" )     flag.BoolVar(&married, "married" , false , "婚否" )     flag.DurationVar(&delay, "d" , 0 , "延迟的时间间隔" )          flag.Parse()     fmt.Println(name, age, married, delay)          fmt.Println(flag.Args())          fmt.Println(flag.NArg())          fmt.Println(flag.NFlag()) } 
 
2、查看帮助 1 2 3 4 5 6 7 8 9 C:\aaa\gin_demo>  go  run main.go  --help   -age int          年龄 (default  18 )   -d duration         延迟的时间间隔   -married         婚否   -name string          姓名 (default  "张三" ) 
 
3、flag参数演示 1 2 3 4 5 C:\aaa\gin_demo>  go  run main.go  -name pprof --age 28  -married=false  -d=1 h30m pprof 28  false  1 h30m0s [] 0 4 
 
4、非flag命令行参数 1 2 3 4 5 C:\aaa\gin_demo>go  run main.go  a b c 张三 18  false  0 s [a b c] 3 0