golang笔记

go的诞生是为了解决 google 内部的问题:

多核硬件架构

超大规模分布式计算集群

WEB 模式导致的 扩大的开发规模和更新速度

go语言的特点

简单 
    
    关键字:    
             C     37  
             go    25
             C++   87 
             
    

高效

    垃圾回收   +   指针


生产力

    复合 

开发环境搭建以及程序入口

GOPATH

1.8-  需要设置

1.8+  使用默认值
        
        UNIX        $HOME/go
        WINDOWS     %USERPROFILE%/go
        
        MAC  -- 修改 ~/.bash_profile来设置 

基本代码结构

package main  // 包, 表示代码所在的模块(包)

import "fmt"  // 引入代码模块

// 功能实现
func main() {
	fmt.Println("hello world!")
}

关于应用程序的入口

- 必须是`main包 , package main`

- 必须是`main方法, func main`

- 文件名不一定是 main.go
  • 关于入口程序退出的返回值

    go中main函数本身不支持返回值, 需要使用 os.Exit()来指定

  • 关于入口程序获取命令行参数

    go中main函数不支持传入参数, x幼通过 os.Args获取命令行参数

变量, 常量

编写测试程序

文件名:   xxx_test.go 

方法名:    func TestXXX(t *testing.T){...}
package try_test

import "testing"

func TestFirstTry(t *testing.T){
	t.Log("my first trial")
}

package fib

import (
	"testing"
)

func TestFibList(t *testing.T){
	var a int = 1
	var b int = 1

	//var(
	//	a int = 1
	//	b int = 1
	//)

	//a := 1     类型推断
	//b := 1
	t.Log(a)
	for i:= 0; i < 6; i++{
		t.Log(b)
		tmp := a
		a = b
		b = tmp + a
	}
}

变量赋值

可以进行  类型推断

在一个赋值语句中可以对多个变量进行   同时赋值

// 变量交换值
func TestChange(t *testing.T){
	a := 1
	b := 2

	t.Log(a, b)

	tmp := a
	a = b
	b = tmp

	t.Log(a, b)

	a, b = b, a

	t.Log(a, b)

}

常量定义 go可以设置连续值iota

const (
    Monday = iota + 1
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
    Sunday
)

// bit 位的操作 
const (
    Open = 1 << iota     第一个比特位为1, 其他为0
    Close                第二位为 1, 其他为0
    Pending              第三位为 1, 其他为0
)
package fib

import (
	"testing"
)

const (
	Readable = 1 << iota
	Writable
	Executable
)

func TestConst(t *testing.T){
	a := 7 // 0111
	t.Log(a&Readable==Readable, a&Writable==Writable, a&Executable==Executable)
	// true,  true,  true
	b :=8 // 1000
	t.Log(b&Readable==Readable, b&Writable==Writable,b&Executable==Executable)
    // false,  false,  false
}

数据类型

bool 

string 

int int8 int16 int32 int64

uint unit8 uint16 uint32 uint64   无符号整型

byte  (uint8的一种) 

rune  (int32的一种, 代表Unicode的一种编码值)

float32 float64 

complex64  complex128

go 不支持隐式类型转换

  • go 不支持隐式类型转换

      var a int32 = 1 
      var b int64 
        
      b = a 
      cannot use a (type int32) as type int64 in assignment
    
  • 别名原有数据类型也不能进行隐式的类型转换

      type MyInt int64
        
      var b int64 = 1
        
      var c MyInt
        
      c = b
      cannot use b (type int64) as type MyInt in assignment
    

指针使用是有限制

不支持指针运算

string 是值类型, 默认的初始化值是空字符串, 不是nil
package type_test

import "testing"


func TestPoint(t *testing.T){
	a := 1
	aPtr := &a
	t.Log(a, aPtr)      // 1 0xc042008278

	var s string
	t.Log("*" + s + "*" ) // **
}

运算符

逻辑运算符

&&	逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。	(A && B) 为 False

||	逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。	(A || B) 为 True

!	逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。	!(A && B) 为 True

比较运算符

package test_compare

import "testing"

func TestCompare(t *testing.T){
	a := [...]int{1, 2, 3}
	b := [...]int{1, 2, 3}
	c := [...]int{2, 3, 4}
	//d := [...]int{1,2}

	t.Log(a == c)
	t.Log(a == b)
	//t.Log(a == d)  数组长度不同
}

位运算符

&	按位与  

|	按位或  

^	按位异或  

<<	左移运算符   左移n位就是乘以2的n次方。 其功能把"<<"左边的运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0。	A << 2 结果为 240 ,二进制为 1111 0000

>>	右移运算符   右移n位就是除以2的n次方。 其功能是把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数。	A >> 2 结果为 15 ,二进制为 0000 1111
package test_compare

import "testing"

func TestCompare(t *testing.T){
	t.Log(6 | 8)  // 按位或     0110   1000  ->   1110
	t.Log(2 ^ 3)  // 按位异或   0010   0011  ->   0001
	t.Log(3 & 4)  // 按位与     0011   0100  ->   0000

}

  • 按位清零运算符

&^按位清零

右边的操作数    二进制位为   1  左边对应的二进制位都为   0
                二进制位为   0  左边对应的二进制位   不变
package test_compare

import "testing"

func TestCompare(t *testing.T){
	t.Log(1 &^ 2)  //1        0001    0010  ->   0001 
	t.Log(2 &^ 1)  //2        0010    0001  ->   0010
	t.Log(0 &^ 2)  //0        0000    0010  ->   0000
	t.Log(2 &^ 0)  //2        0010    0000  ->   0010
}

条件, 循环

  • go 中的循环都是用 关键字for完成

      // while n<6
      for n := 0; n < 6; n++{
          t.Log(n)
      }
        
        
      // while true
      for{
          t.Log("in")
      }
    
  • 条件分支

if else

package test_compare

import "testing"


func someFunc() (res int32, err error) {
	res = 1
	return res, nil
}

func TestIf(t *testing.T){
	if v, err := someFunc(); err != nil{
		t.Log("error")
	}else{
		t.Log(v)
	}

}

switch

for i:=0; i<5; i++ {
		switch i {
		case 0, 2: // case后面加多个条件
			t.Log("Event")
		case 1, 3:
			t.Log("Odd")
		}
	}
	

for i:=0; i<10; i++{
		switch {
		case  i % 2 == 0:
			t.Log("event")
		case  i % 2 == 1:
			t.Log("odd")
		default:
			t.Log("unknown")
		}
	}

数组与切片

数组的声明

var a [3]int    // 声明并且初始化为默认值 0 
a[0] = 1


b := [3]int{1, 2, 4}                  // 声明同时初始化   1维数组

c := [2][2]int{ {1, 2}, {3, 4} }         // 声明同时初始化  多维数组

arr3 := [...]int{1, 3, 5, 6, 7}       // 不定长度 

数组的遍历


func TestArrayTravel(t *testing.T){
	arr3 := [...]int{1, 2, 4, 5, 6}
    
    // 常规方式
	for i:=0;i<len(arr3);i++{
		t.Log(arr3[i])
	}

    // for ... range
	for idx, e := range arr3{
		t.Log(idx, e)
	}

    // 使用 _ 占位符
	for _, e := range arr3{
		t.Log(e)
	}
}

数组的截取

a := [...]int{1, 2, 3, 4, 5}

a[1:2]  // 2 
a[1:3]  // 2. 3
a[1:len(a)] // 2, 3, 4, 5
a[1:]   // 2, 3, 4, 5
a[:3]   //1, 2, 3

切片的内部结构 可变长的数组

实际是一个结构体 
    
    ptr     --->       连续的存储空间(数组)
    
    len               元素的个数
    
    cap               内部数组的容量

初始化

var s []int
s = append(s, 1)    // 添加元素使用 append
                    // 分配新的内存空间, 数据重新拷贝, 赋值

s1 := []int{1, 2, 4}

s2 := make([]int, 3, 5)  // 类型, len, cap

切片cap 的增长规律

func TestSliceGrowing(t *testing.T){
	s := []int{}
	for i:=0;i<10;i++{
		s = append(s, i)
		t.Log(len(s), cap(s))
	}
}
    // 每次cap不够用, 增加都是之前的 2倍
	1 1      
	2 2      
	3 4     
	4 4
	5 8
	6 8
	7 8
	8 8
	9 16
	10 16
	

切片共享存储空间


func TestSliceShareMemory(t *testing.T){
	year := [...]string{"", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
		"Aug", "Sep", "Oct", "Nov", "Dec"}

	Q2 := year[4:7]
	t.Log(len(Q2), cap(Q2))  //  3,  9

	Q3 := year[6:9]
	t.Log(len(Q3), cap(Q3))  // 3,  7

	Q3[0] = "unknown"
	t.Log(Q2)              // [Apr May unknown]
	
}

数组, 切片的对比

slice can only be compared to nil

数组:   容量不可以伸缩
        
        相同容量, 维度的数组可以进行比较


切片:   容量可以伸缩
        slice can only be compared to nil

Map

Map 的声明

m := map[string]int{"one":1, "two":2}
m1 := map[string]int{}

m2 := make(map[string]int, 10)    // 制定容量

Map 元素的访问

访问的key不存在时 会返回零值而不是空

m1 := map[int]int{}
t.Log( m1[1])  // 不存在的key , 获取到value是0

if v, ok := m1[1]; ok{
    t.Log("key 1", "exits", v)
}else{
    t.Log("key 1", "not exists")
}

Map 遍历

m1 := map[int]int{1:2, 2:4, 3:6}
for k, v := range m1{
    t.Log(k, v)
}

// 无序

使用map实现工厂模式 – value 默认值为 函数

m1 := map[int]func(op int) int{}

m1[1] = func(op int) int {return op}
m1[2] = func(op int) int {return op*op}
m1[3] = func(op int) int {return op*op*op}

t.Log(m1[1](2), m1[2](2), m1[3](2))

使用map实现集合 Setvalue值为bool

Map[type]bool

1 元素唯一性
2 基本操作
    
    添加元素
    判断元素是否存在
    删除元素
    元素个数

func TestMapForSet(t *testing.T){
	mySet := map[int]bool{}   // 初始化一个 默认value为false的 map
	mySet[1] = true

	n := 2
	t.Log(mySet[3]) // false
	if mySet[n]{
		t.Logf("%d is existing", n)
	}else{
		t.Logf("%d is not existing", n )
	}
}

字符串 string

  • string 是数据类型, 不是 引用或者指针类型

  • string 是只读byte slice

    len 可以获取所包含的byte数量

  • string 的 byte数组可以存放任何的二进制数据

var s string
	t.Log(s) // 初始化为""

	s = "hello"
	t.Log(s, len(s), s[1])

	// string 是 不可改变的  byte slice
	//s[1] = 3 // cannot assign to s[1]

	s = "中"
	t.Log(s, len(s), s[1])

	s = "\xE4\xB8\xA5"// 三个byte
	t.Log(s, len(s), s[1])


unicodeutf-8

unicode 是一种字符集 (code  point 编码)

utf-8 是 unicode的存储实现 (转换为字节序列的规则)


    
    字符                      "中"
    
    Unicode                   0x4e2d   (编码集中的编码)
    
    UTF-8                     0xe4b8ad  (物理存储 的规则)
    
    string/[]byte             [0xe4, 0xb8, 0xad]   放在string的对应的byte切片

遍历字符串的元素时,输出的是rune -- int32 unicode编码

	s := "中华人民共和国"

	for _, c := range s {
		//t.Logf("%c", c)
		//t.Logf("%d", c)
		//t.Logf("%x", c)
		t.Logf("%[1]c %[1]d %[1]x", c)   // [1] 表示都用 c 参数格式化
	}
         中 20013 4e2d
         华 21326 534e
         人 20154 4eba
         民 27665 6c11
         共 20849 5171
         和 21644 548c
         国 22269 56fd

字符串函数

  • strings
	s := "a,b,c"
	parts := strings.Split(s, ",")  // 分割
	t.Log(parts)  // [a b c]

	s1 := strings.Join(parts, "-")  // 连接
	t.Log(s1) // a-b-c

  • strconv 包 – 转换
// 数字转字符串
s = strconv.Itoa(10) //  Itoa is shorthand for FormatInt(int64(i), 10).
t.Log("str" + s)  // str10

// 字符串转数字
i,_ := strconv.Atoi("10")
t.Log(10 + i)    // 20


函数

go函数的特点

1. 可以有多个返回值

2. 所有参数都是值传递:slice,map,channel 会有传引⽤的错觉

3. 函数可以作为变量的值

4. 函数可以作为参数和返回值

#  函数式编程 (增加 耗时统计 -- 类装饰器)



func timeSpent(inner func(op int) int ) func(op int) int {
	return func(op int) int {
		start := time.Now()
		ret := inner(op)
		fmt.Println("time spent: ", time.Since(start).Seconds())
		return ret
	}
}

func slowFunc(op int) int {
	time.Sleep(time.Second*1)
	return op
}


func TestFn(t *testing.T){
	tsSF := timeSpent(slowFunc)
	t.Log(tsSF(10))
}

可变长参数 以及 defer关键字

#  可变长参数
 
func Sum(ops ...int) int {
	ret:=0
	// 数组接收
	for _, op := range ops{
		ret += op
	}
	return ret
}

func TestVarParam(t *testing.T){
	t.Log(Sum(1, 2, 3, 4))
	t.Log(Sum(1, 2, 3))
}

defer 延迟执行函数

类似以 try ... finally ... 最后执行
  

清理资源 释放锁  

func TestDefer(t *testing.T){
	defer func(){
		t.Log("CLear resources")
	}()

	t.Log("started")
	panic("Fatal error")  // 异常情况下, defer仍会执行

}

go中的面向对象编程

关于go是不是面向对象编程语言:

也是也不是:  不支持继承 但是可以实现 objectoriented style of programming



面向对象 -->> 继承

go 不支持继承

    Go具有类型和方法,并允许使用的编程风格,但是没有类型层次结构。
    
    Go中的“接口”概念提供了一种不同的方法,我们认为该方法易于使用,并且在某些方面更通用。
    
    另外,缺少类型层次结构使得Go中的“对象”比C ++或Java等语言更轻量。

封装 (数据 struct + 行为)

  • 数据的封装结构体 struct

结构体的定义:

type Employee struct {
 Id string
 Name string
 Age int
}

实例创建和初始化:

e := Employee{"0", "Bob", 20}
e1 := Employee{Name: "Mike", Age: 30}

e2 := new(Employee) //注意这⾥返回的 (指向实例的引⽤/指针),相当于 e := &Employee{}


//与其他主要编程语⾔的差异:通过实例的指针访问成员不需要使⽤ ->
e2.Id = “2" //直接 . 
e2.Age = 22
e2.Name = “Rose"
 
func TestCreateEmployeeObj(t *testing.T){
e := Employee{"0", "Bob", 22}  // &{Id2 Name2 23}

e2 := new(Employee)
e2.Id = "Id2"
e2.Name = "Name2"
e2.Age = 23  //  &{Id2 Name2 23}

t.Log(e, e2)
t.Logf("e is %T", e)     // e is test_string.Employee
t.Logf("&e is %T", &e)   // &e is *test_string.Employee
t.Logf("e2 is %T", e2)   //e2 is *test_string.Employee

}
  • 对行为的封装

行为(方法的定义):


两种定义方式: (都可以用实例或者实例指针直接调用)



(1)
// 第⼀种定义⽅式在实例对应⽅法被调⽤时,实例的成员会进⾏值复制
func (e Employee) String() string {
	return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age)
}

//e := Employee{"0", "Bob", 22}  // &{Id2 Name2 23}
//fmt.Printf("Address is %x\n", unsafe.Pointer(&e.Name))
//t.Log(e.String()) // 这里会对值进行值复制



(2)
// 通常情况下为了  避免内存拷⻉   我们使⽤第⼆种定义⽅式
func (e *Employee) String() string{
	return fmt.Sprintf("ID:%s/Name:%s/Age:%d", e.Id, e.Name, e.Age)
}

//e := Employee{"0", "Bob", 22}  // &{Id2 Name2 23}
//fmt.Printf("Address is %x\n", unsafe.Pointer(&e.Name))
//t.Log(e.String()) // 这里不会对值进行值复制




定义交互协议(接口)

接⼝为⾮⼊侵性,实现不依赖于接⼝定义

所以接⼝的定义可以包含在接⼝使⽤者包内

接口定义:

type Programmer interface {
    WriteHelloWorld() string
}

接口实现:

type GoProgrammer struct {
}

func (p *GoProgrammer) WriteHelloWorld() Code {
    return "fmt.Println(\"Hello World!\")"
}

接口变量:

var p Programmer = &GoProGrammer{}


p 包含两个部分:

       类型:  type GoProgrammer struct {}
        
       数据:   &GoProgrammer{}

自定义类型:

type IntConvertionFn func(n int) int

type MyPoint int

# 
func timeSpent(inner func(op int) int ) func(op int) int {
	return func(op int) int {
		start := time.Now()
		ret := inner(op)
		fmt.Println("time spent: ", time.Since(start).Seconds())
		return ret
	}
}


# 简化
type IntConv func(op int) int

func timeSpent(inner IntConv) IntConv {
	return func(op int) int {
		start := time.Now()
		ret := inner(op)
		fmt.Println("time spent: ", time.Since(start).Seconds())
		return ret
	}
}
 

扩展与复合

Go 不⽀持继承,但可以通过复合的⽅式来复⽤


type Pet struct{
}

func (p *Pet) Speak(s string ){
	fmt.Println("...")
}

func (p *Pet) SpeakTo(s string , host string){
	p.Speak(s)
	fmt.Println(" ", host)
}


type Dog struct{
	Pet
}

func (d *Dog) Speak(){
	fmt.Println("wang wang")
}



func TestDog(t *testing.T){
	dog := new(Dog)
	dog.SpeakTo("wang", "cat")
}


    不是继承
    
    “内部 struct ”  看作⽗类  Pet 
    
     “外部 struct” 看作⼦类  Dog
     
        
        不⽀持⼦类替换
        
        ⼦类并不是真正继承了⽗类的⽅法 (⽆法访问⼦类的数据和⽅法)

        

多态空接⼝

多态


type Code string

type Programmer interface {
	WriteHelloWorld() Code
}

type GoProgrammer struct{
}
func (g *GoProgrammer) WriteHelloWorld() Code{
	return "fmt.Println(\"hello world\")"
}

type PythonProgrammer struct{
}
func (p *PythonProgrammer) WriteHelloWorld() Code{
	return "print(\"hello world\")"
}

type JavaProgrammer struct{
}
func (j *JavaProgrammer) WriteHelloWorld() Code{
	return "System.out.Println(\"hello world\")"
}

// %T 输出 类型
func WriteFirstProgram(p Programmer){
	fmt.Printf("%T %v\n", p, p.WriteHelloWorld())
}

func TestPolymorphism(t *testing.T){
	goProg := new(GoProgrammer)
	// goProg := &GoProgrammer{}  指针
	javaProg := new(JavaProgrammer)
	pythonProg := new(PythonProgrammer)

	WriteFirstProgram(goProg)
	WriteFirstProgram(pythonProg)
	WriteFirstProgram(javaProg)
}
 

空接口 与 断言

空接口可以表示任何数据类型

通过断言来将空接口 转换 成 指定类型

    v, ok := p.(int)   // 
(1)
func DoSomething(p interface{}){
	if i, ok := p.(int);ok{
		fmt.Println("Integer", i)
		return 
	}
	if i, ok := p.(string); ok {
		fmt.Println("String", i)
		return
	}
	fmt.Println("unknown")
}

func TestEmpty(t *testing.T) {
	DoSomething(12)
	DoSomething("11")
	DoSomething(true)
} 



(2)
func DoSomething(p interface{}){
	switch v:=p.(type){
		case int: fmt.Println("int", v)
		case string: fmt.Println("string", v)
		case bool: fmt.Println("bool", v)
	default:
		fmt.Println("unknown")
	}
}

func TestEmpty(t *testing.T) {
	DoSomething(12)
	DoSomething("11")
	DoSomething(true)
	DoSomething([...]int{1, 2, 3})
	DoSomething(map[string]bool{})
}

Go 接口的最佳实践

倾向于使⽤⼩的接⼝定义,很多接⼝只包含⼀个⽅法                  type Reader interface {
                                                              Read(p []byte) (n int, err error)
                                                             }
                                                             
                                                            type Writer interface {
                                                              Write(p []byte) (n int, err error)
                                                            }
            

较⼤的接⼝定义,可以由多个⼩接 ⼝定义组合⽽成                  type ReadWriter interface {
                                                             Reader
                                                             Writer
                                                            }  
                                                        

只依赖于必要功能的最⼩接⼝                                 func StoreData(reader Reader) error {
                                                             …
                                                            }  

异常处理

Go 中的错误机制:

1. 没有异常机制


2. error 类型实现了 error 接⼝              type error interface {
                                             Error() string


3. 可以通过 errors.New 来快速创建错误实例    errors.New("n must be in the range [0,100]")

最佳实践:

`定义不同的错误变量`,以便于`判断错误类型`

提早, 快速报错

var FabError error = errors.New("n must be greater than 2 and less than 100")

func GetFab(n int) ([]int, error){
	if n < 2 || n > 100{
		return nil, FabError
	}
	fiblist := []int{1, 1}

	for i:= 2; i<n; i++{
		fiblist = append(fiblist, fiblist[i-2] + fiblist[i-1])
	}
	return fiblist, nil
}


func TestError(t *testing.T){
	if v, err := GetFab(-1); err != nil {
		if err == FabError{
			t.Error(err)
		}else{
			err := errors.New("other err")
			t.Error(err)
		}
	}else{
		t.Log(v)
	}
}

panicrecover

panic

• panic ⽤于不可以恢复的错误

• panic 退出前会执⾏ defer 指定的内容

os.Exit vs panic

• os.Exit 退出时不会调⽤ defer 指定的函数

• os.Exit 退出时不输出当前调⽤栈信息 (异常的产生调用过程信息)

recover 错误恢复

错误的使用 错误恢复 的问题:

    形成僵⼫服务进程,导致 health check 失效。
   
    “Let it Crash!” 往往是我们恢复不确定性错误的最好⽅法


Java        try{...} catch(Throwable t){}

C++         try{...} catch(...){}

python      try: ... Except Exception e: ...
func TestError(t *testing.T){
	defer func(){
		if err := recover(); err != nil{
			fmt.Println("recovered from ", err)  // recovered from  something error
		}
	}()

	panic(errors.New("something error"))
}

依赖管理

package·包

1. 基本复⽤模块单元

     以 ⾸字⺟⼤写(函数) 来表明可被包外代码访问
     
2. 代码的 package 可以和所在的⽬录不⼀致

3. 同⼀⽬录⾥的 Go 代码的 package 要保持⼀致


export  GOPATH="/path/to/project"

├─pacakge1
│  ├─component1
│  │   └─base.go
├─pacakge2
│  ├─test_package
         └─run.go
# base.go 

package component1

func String(i interface{}) string {
	if v, ok := i.(string); ok{
		return v
	}else{
		return ""
	}}

func intI(i interface{}) int {
	if v, ok := i.(int); ok{
		return v
	}else{
		return 0
	}}



# run.go

package test_package

import (
	"pacakge1/component1"
	"testing"
)


func TestPackage(t *testing.T){
	t.Log(component1.String(1)) //  ⾸字⺟⼤写(函数) 来表明可被包外代码访问
}
 

go get 获取远程依赖

通过 go get 来获取远程依赖  
go get -u 强制从⽹络更新远程依赖


上传的时候:

    注意代码在 GitHub 上的组织形式,以适应 go get
    直接以代码路径开始,不要有 src

init 方法

按照包导⼊的依赖关系 顺序执⾏

• 在 main 被执⾏前,所有依赖的 package 的 init ⽅法都会被执⾏

• 不同包的 init 函数按照包导⼊的依赖关系决定执⾏顺序

• 每个包可以有多个 init 函数

• 包的每个源⽂件也可以有多个 init 函数,这点⽐较特殊


func init(){
	fmt.Print("init 1")
}

func init(){
	fmt.Print("init 2")
}

依赖管理

vendor 路径

查找依赖包路径的解决⽅案如下:

1. 当前包下的 vendor ⽬录
2. 向上级⽬录查找,直到找到 src 下的 vendor ⽬录
3. 在 GOPATH 下⾯查找依赖包
4. 在 GOROOT ⽬录下查找

godep

glide

dep

Go 协程机制

不同语言 创建线程的初始栈 大小对比:

Python         

Java (JDK5+)   Thread stack 默认为1M

Groutine         Stack 初始化⼤⼩为2K  (创建起来更快)

不同语言 thread (用户级别线程) user space threadKSE (Kernal Space Entity)系统线程(内核对象)`的对应关系

Java Thread     1: 1

Goroutine       M: N 


KSE 由 CPU直接调度
    
    效率高
    
    线程切换(内核对象切换) 开销大
    
    用户线程 与 内核线程 多对多 (可以减少开销)

go 协程的基本调度机制:

M  --  系统线程  kernal entity

P  --  Go 实现的 协程处理器(逻辑处理器) (依次运行队列中的 协程)

G  --  协程

    在go启动的时候, 会有一个 守护线程 (统计Processor中 完成的协程的数量)
    
    当一段时间 数量不变的 时候, 会往 当前的协程 中插入一个标志, 遇到IO时候会读到 这个标志
    
    暂停当前的协程(创建一个新的线程),切换其他协程
func TestGoroutine(t *testing.T){
   for i:=0; i<10; i++{
   	go func(i int){
   		fmt.Print(i)
	}(i)
   }

   time.Sleep(time.Second*1)
}

共享内存 并发机制

Lock sync.Mutex

package sync 

Mutex   互斥

RWLock   可以并发写,写互斥 (推荐)

func  TestLock(t *testing.T){
	counter := 0
	for i:=0; i<5000; i++{
		go func(){
			counter++
		}()
	}
	time.Sleep(1*time.Second)
	t.Logf("counter = %d", counter) // 4711

}


func  TestLockThreadSafe(t *testing.T){
	var mut sync.Mutex
	counter := 0
	for i:=0; i<5000; i++{
		go func(){
			defer func(){
				mut.Unlock()
			}()
			mut.Lock()
			counter++
		}()
	}
	time.Sleep(1*time.Second)
	t.Logf("counter = %d", counter) // 4711

}

WaitGroup 类似于 join

var  wg sync.WaitGroup

wg.Add(1)  --  等待的事件 +1
wg.Done()  --  已经完成

wg.Wait()  --  主线程 等待 其他事件完成 
 
func  TestLockThreadSafe(t *testing.T){
	var mut sync.Mutex
	var wg sync.WaitGroup
	counter := 0
	for i:=0; i<5000; i++{
		wg.Add(1)
		go func(){
			defer func(){
				mut.Unlock()
			}()
			mut.Lock()
			counter++
			wg.Done()
		}()
	}
	wg.Wait()
	t.Logf("counter = %d", counter) // 4711

}
 

CSP (Communicating Sequential Processes)并发机制

Actor Model

CSP vs Actor

CSP Actor
通过Channel进⾏通讯耦合松 直接通讯
channel有容量限制 mailbox容量⽆限
独⽴于 Groutine, 主动处理 接收进程 总是被动地处理消息

两种Channel模式:

无缓冲的通道


有缓冲的通道 

func service() string {
	time.Sleep(time.Microsecond * 59)
	return "service Done"
}

func AsyncService() chan string{
	retCh := make(chan string)
	go func(){
		ret := service()
		fmt.Println("return results")
		retCh <- ret
		fmt.Println("service exited")
	}()
	return retCh
}

func otherTask(){
	fmt.Println("otherTask")
	time.Sleep(time.Microsecond * 59)
	fmt.Println("other task Done")
}

func TestChannel(t *testing.T){
	fmt.Println(service())
	otherTask()
}

func TestAsyncChannel(t *testing.T){
	retCh := AsyncService()
	otherTask()
	fmt.Println( <- retCh)
} 

多路选择 select 超时控制

case 后边跟着的是阻塞事件

如果有default, 其他 case都是阻塞状态, 会选择default 执行 
#  多渠道的选择
select {
case ret := <-retCh1:       // 从 retCh1 等待消息 
    t.Logf("result %s", ret)
case ret :=<-retCh2:       // 从 retCh2 等待消息 
     t.Logf("result %s", ret)
 default:                  //
    t.Error(No one returned)
}



# 超时控制
select {
case ret := <-retCh:
    t.Logf("result %s", ret)
case <-time.After(time.Second * 1):  
    t.Error("time out")
}




# 例子
func TestTimeoutChannel(t *testing.T){
	select{
	case ret := <- AsyncService():
		t.Log(ret)
	case <- time.After(time.Microsecond*45):
		t.Error("timeout!")
	}
}


channel 关闭 和 广播

1 向关闭的 channel 发送数据,会导致 panic

2 
    • v, ok <-ch; ok 为 bool 值,true 表示正常接受,false 表示通道关闭

    • 所有的 channel 接收者都会在 channel 关闭时,⽴刻从阻塞等待中返回且上
      述 ok 值为 false。
  
3  如果channel 已经关闭, 再从里面取值 <-ch,  会获取 类型值的默认值
                
             int  0
             string  ""
                
      
 这个⼴播机制常被利⽤,进⾏向多个订阅者同时发送信号。 

func dataProducer(ch chan int, wg*sync.WaitGroup){
	go func(){
		for i:=0; i<10; i++{
			ch <- i
		}
		close(ch)  // 关闭通道 (广播)
		// ch <- 10   panic: send on closed channel
		wg.Done()
	}()
}


func dataReceiver(ch chan int, wg *sync.WaitGroup){
	go func(){
		for {
			if data, ok := <-ch; ok{
				time.Sleep(time.Microsecond * 4000)
				fmt.Println(data)
			}else{   // 收到关闭channel的信息
				fmt.Print(ok)
				break
			}
		}
	wg.Done()
	}()
}

func TestCloseChannel(t *testing.T){
	var wg sync.WaitGroup
	ch := make(chan int)
	wg.Add(1)
	dataProducer(ch, &wg)
    
    // 多个消费者
	wg.Add(1)
	dataReceiver(ch, &wg)
	wg.Add(1)
	dataReceiver(ch, &wg)
	wg.Add(1)
	dataReceiver(ch, &wg)

	wg.Wait()
}

任务的取消 close(channel) -- 广播


// 获取取消通知
func isCanceled(cancelChan chan struct{})bool{
	select{
	case <- cancelChan:
		// channel 关闭 依然可以使阻塞被唤起
		return true
	default:
		return false
	}
}


func cancel_1(cancelChan chan struct{}){
	cancelChan <- struct{}{}
	// 单个发送取消消信息
}


func cancel_2(cancelChan chan struct{}){
	close(cancelChan)
	// 广播
}

func TestCancel(t *testing.T){
	cancelChan := make(chan struct{})
	for i:= 0; i<5; i++{
		go func(i int, cancelChan chan struct{}){
			for{
				if isCanceled(cancelChan){
					break
				}
				time.Sleep(time.Microsecond * 5)
			}
			fmt.Println(i, "Done")
		}(i, cancelChan)
	}
	cancel_1(cancelChan) // 只发一个cancel信号


	cancel_2(cancelChan) // 关闭 channel, 广播信号给所有的goroutine

	time.Sleep(time.Second * 1)
} 

关联任务的取消 Context 上下文 v1.9+

Context

• 根 Context:通过 context.Background () 创建

• ⼦ Context:context.WithCancel(parentContext) 创建

    • ctx, cancel := context.WithCancel(context.Background())

• 当前 Context 被取消时,基于他的⼦ context 都会被取消

• 接收取消通知 <-ctx.Done()

func isCanceled(ctx context.Context )bool{
	select{
	case <- ctx.Done():
		//
		return true
	default:
		return false
	}
}


func TestContextCancel(t *testing.T){
	ctx, cancel := context.WithCancel(context.Background())
	for i:= 0; i<5; i++{
		go func(i int, ctx context.Context){
			for{
				if isCanceled(ctx){
					break
				}
				time.Sleep(time.Microsecond * 5)
			}
			fmt.Println(i, "Done")
		}(i, ctx)
	}
	cancel()


	time.Sleep(time.Second * 1)
}

Buy me a 肥仔水!