九折技术 九折技术
首页
  • Go
  • MIT-6824
  • 算法与数据结构
  • 面向对象
  • 代码整洁
  • 重构
  • 设计模式
  • 学习
  • 技术
  • 人文
关于
  • 网站
  • 资源
  • 分类
  • 标签
  • 归档
GitHub

HoldDie

长期有耐心,一切才刚刚开始!
首页
  • Go
  • MIT-6824
  • 算法与数据结构
  • 面向对象
  • 代码整洁
  • 重构
  • 设计模式
  • 学习
  • 技术
  • 人文
关于
  • 网站
  • 资源
  • 分类
  • 标签
  • 归档
GitHub
  • Go

    • Go语言基础
      • Go语言 roadmap
        • Go 语⾔言基础
        • 进阶与实战
        • 软件开发的新挑战
        • Go 的创始⼈人
        • Go 语⾔言发展
        • 准备开始 Go 冒险之旅
        • 开发环境构建
        • 基本程序结构
        • 应⽤用程序⼊入⼝口
        • 退出返回值
        • 获取命令⾏行行参数
        • 编写测试程序
        • 实现 Fibonacci 数列列
        • 1, 1, 2, 3, 5, 8, 13, ....
        • 变量量赋值
        • 常量量定义
        • 基本数据类型
        • 类型转化
        • 类型的预定义值
        • 指针类型
        • 算术运算符
        • ⽐比较运算符
        • 逻辑运算符
        • 位运算符
        • 位运算符
        • 循环
        • 代码示例例
        • if 条件
        • switch 条件
        • switch条件
        • 数组的声明
        • 数组元素遍历
        • 数组截取
        • 切⽚片内部结构
        • 切⽚片声明
        • 数组 vs. 切⽚片
        • Map 声明
        • Map 元素的访问
        • Map 遍历
        • Map 与⼯工⼚厂模式
        • 实现 Set
        • 字符串串
        • Unicode UTF8
        • 编码与存储
        • 常⽤用字符串串函数
        • 函数是⼀一等公⺠民
        • 学习函数式编程
        • 可变参数
        • defer 函数
        • 结构体定义
        • 实例例创建及初始化
        • 行为(方法)定义
        • 接⼝口与依赖 Programmer.java
        • Duck Type 式接⼝口实现
        • Go 接口
        • 自定义类型
        • 复合
        • 匿名类型嵌⼊
        • 多态
        • 空接⼝口与断⾔言
        • Go 接⼝口最佳实践
        • Go 的错误机制
        • 最佳实践
        • 最佳实践
        • panic
        • panic vs. os.Exit
        • recover
        • recover
        • 最常见的”错误恢复“
        • 当⼼心! recover 成为恶魔
        • package
        • package
        • init ⽅方法
        • Go 未解决的依赖问题
        • Thead vs. Groutine
        • Lock、Mutex、RWLock
        • WaitGroup
        • CSP vs. Actor
        • Channel异步返回
        • select
        • channel 的关闭
        • 获取取消通知
        • 发送取消消息
        • 通过关闭 Channel 取消
        • Context
        • 单例例模式 (懒汉式,线程安全)
        • 单例例模式 (懒汉式,线程安全)
        • sync.Pool 对象获取
        • sync.Pool 对象的放回
        • 使⽤用 sync.Pool
        • sync.Pool 对象的⽣生命周期
        • sync.Pool 总结
        • 内置单元测试框架
        • 内置单元测试框架
        • Benchmark
        • Benchmark
  • MIT-6824

  • 后端
  • Go
holddie
2020-08-14

Go语言基础

# Go语言 roadmap

# Go 语⾔言基础

  • 基本程序结构
  • 常⽤用集合
  • 函数式编程
  • ⾯面向对象编程
  • 错误处理理
  • 模块化及依赖管理

# 进阶与实战

  • 并发编程模式
  • 常⻅见并发任务
  • 深⼊入测试
  • 反射和 Unsafe
  • 常⻅见架构模式的实现
  • 性能调优
  • ⾼高可⽤用性服务设计

# Go 语⾔言简介

# 软件开发的新挑战

  1. 多核硬件架构
  2. 超⼤大规模分布式计算集群
  3. Web 模式导致的前所未有的开发规模和更更新速度

# Go 的创始⼈人

  • Rob Pike
  • Unix 的早期开发者
  • UTF-8 创始⼈人
  • Ken Thompson
    • Unix 的创始⼈人
    • C 语⾔言创始⼈人
    • 1983 年年获图灵奖
  • Robert Griesemer
    • Google V8 JS Engine 开发者
    • Hot Spot 开发者

# Go 语⾔言发展

# 准备开始 Go 冒险之旅

下载安装 Go 语⾔言
https://golang.org/doc/install
https://golang.google.cn/dl/

安装 IDE
Atom: https://atom.io + Package: go-plus
1
2
3
4
5
6

# 编写第⼀一个 Go 程序

# 开发环境构建

GOPATH

  1. 在 1.8 版本前必须设置这个环境变量量
  2. 1.8 版本后(含 1.8)如果没有设置使⽤用默认值

在 Unix 上默认为 _$HOME/go , 在 Windows 上默认为 _%USERPROFILE%/go_

在 Mac 上 GOPATH 可以通过修改 ~/.bash_profile 来设置

# 基本程序结构

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

import "fmt" //引⼊入代码依赖

//功能实现
func main() {
  fmt.Println("Hello World!")
}
1
2
3
4
5
6
7
8

# 应⽤用程序⼊入⼝口

  1. 必须是 main 包:package main
  2. 必须是 main ⽅方法:func main()
  3. ⽂文件名不不⼀一定是 main.go

# 退出返回值

与其他主要编程语⾔言的差异

  • Go 中 main 函数不不⽀支持任何返回值^
  • 通过os.Exit 来返回状态

# 获取命令⾏行行参数

与其他主要编程语⾔言的差异

  • main 函数不不⽀支持传⼊入参数 func main(arg []string)

  • 在程序中直接通过os.Args 获取命令⾏行行参数

# 变量量与常量量

# The master has failed more times than the beginner has tried.

# 编写测试程序

  1. 源码⽂文件以 _test 结尾:xxx_test.go
  2. 测试⽅方法名以 Test 开头:func TestXXX(t *testing.T) {...}

# 实现 Fibonacci 数列列

# 1, 1, 2, 3, 5, 8, 13, ....

# 变量量赋值

  • 赋值可以进⾏行行⾃自动类型推断^
  • 在⼀一个赋值语句句中可以对多个变量量进⾏行行同时赋值^

与其他主要编程语⾔言的差异

# 常量量定义

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

const (
  Open = 1 << iota
  Close
  Pending
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

快速设置连续值

与其他主要编程语⾔言的差异

# 数据类型

# 基本数据类型

  • bool

  • string

  • int int8 int16 int32 int64

  • uint uint8 uint16 uint32 uint64 uintptr

  • byte // alias for uint8

  • rune // alias for int32,represents a Unicode code point

  • float32 float64

  • complex64 complex128

# 类型转化

  1. Go 语⾔言不不允许隐式类型转换
  2. 别名和原有类型也不不能进⾏行行隐式类型转换

与其他主要编程语⾔言的差异

# 类型的预定义值

  1. math.MaxInt64

  2. math.MaxFloat64

  3. math.MaxUint32

# 指针类型

  1. 不不⽀支持指针运算
  2. string 是值类型,其默认的初始化值为空字符串串,⽽而不不是 nil

# 运算符

# 算术运算符

Go 语⾔言没有前置的 ++,- -,(++a)

运算符 描述 实例例
+ 相加 A + B 输出结果 30
- 相减 A - B 输出结果 -10
* 相乘 A * B 输出结果 200
/ 相除 B / A 输出结果 2
% 求余 B % A 输出结果 0
++ ⾃自增 A++ 输出结果 11
-- ⾃自减 A-- 输出结果 9
1
2
3
4
5
6
7
8

# ⽐比较运算符

运算符 描述 实例例
== 检查两个值是否相等,如果相等返回 True 否则返回 False。 (A == B) 为 False
!= 检查两个值是否不不相等,如果不不相等返回 True 否则返回 False。 (A != B) 为 True
> 检查左边值是否⼤大于右边值,如果是返回 True 否则返回 False。 (A > B) 为 False
< 检查左边值是否⼩小于右边值,如果是返回 True 否则返回 False。 (A < B) 为 True
>= 检查左边值是否⼤大于等于右边值,如果是返回 True 否则返回 False。 (A >= B) 为 False
<= 检查左边值是否⼩小于等于右边值,如果是返回 True 否则返回 False。 (A <= B) 为 True
1
2
3
4
5
6
7
  • 相同维数且含有相同个数元素的数组才可以⽐比较
  • 每个元素都相同的才相等
  • ⽤用 == ⽐比较数组

# 逻辑运算符

运算符 描述 实例例
&& 逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。 (A && B) 为 False
|| 逻辑 OR 运算符。 如果两边的操作数有⼀一个 True,则条件 True,否则为 False。 (A || B) 为 True
!  逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。 !(A && B) 为 True
1
2
3
4

# 位运算符

运算符 描述 实例例
& 按位与运算符各对应的⼆二进位相与。"&"是双⽬目运算符。^ 其功能是参与运算的两数 (A & B) 结果为 12, ⼆二进制为 0000 1100
| 按位或运算符各对应的⼆二进位相或"|"是双⽬目运算符。^ 其功能是参与运算的两数 (A | B) 结果为 61, ⼆二进制为 0011 1101
^ 按位异或运算符数各对应的⼆二进位相异或,当两对应的⼆二进位相异时,结果"^"是双⽬目运算符。^ 其功能是参与运算的两为 1 。(A ^ B) 结果为 49, ⼆二进制为 0011 0001
<< 左移运算符”<<"是双⽬目运算符。左移 n 位就是乘以 2 的 n 次⽅方。 其功能把"<<"左边的运算数的各⼆二进位全部左移若⼲干位,由"<<"右边的数指定移动的位数,⾼高位丢弃,低位补0 。A << 2 结果为 240 ,⼆二进制为 1111 0000
>> 右移运算符”>>"是双⽬目运算符。右移 n 位就是除以 2 的 n 次⽅方。 其功能是把">>"左边的运算数的各⼆二进位全部右移若⼲干位,">>"右边的数指定移动的位数。A >> 2 结果为 15 ,⼆二进制为 0000 1111

&^ 按位置零

1 &^ 0 -- 1
1 &^ 1 -- 0
0 &^ 1 -- 0
0 &^ 0 -- 0
1
2
3
4
5
6
7
8
9
10
11
12
13

# 位运算符

# 编写结构化程序

# 循环

  • for ( j := 7 ; j <= 9 ; j++ )

  • Go 语⾔言仅⽀支持循环关键字 for

# 代码示例例

n := 0
for n < 5 {
  n++
  fmt.Println(n)
}

// while 条件循环
while (n<5)
n := 0
for {
  ...
}

// ⽆无限循环
while (true)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# if 条件

if condition {
  // code to be executed if condition is true
} else {
  // code to be executed if condition is false
}

if condition-1 {
  // code to be executed if condition-1 is true
} else if condition-2 {
  // code to be executed if condition-2 is true
} else {
  // code to be executed if both condition1 and condition2 are false
}
1
2
3
4
5
6
7
8
9
10
11
12
13
  1. condition 表达式结果必须为布尔值
  2. ⽀支持变量量赋值:
if var declaration; condition {
  // code to be executed if condition is true
}
1
2
3

# switch 条件

switch os := runtime.GOOS; os {
  case "darwin":
  fmt.Println("OS X.”)
              //break
              case "linux":
              fmt.Println("Linux.")
              default:
              // freebsd, openbsd,
              // plan9, windows...
              fmt.Printf("%s.", os)
             }

  switch {
    case 0 <= Num && Num <= 3:
    fmt.Printf("0-3")
    case 4 <= Num && Num <= 6:
    fmt.Printf("4-6")
    case 7 <= Num && Num <= 9:
    fmt.Printf("7-9")
  }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# switch条件

  1. 条件表达式不不限制为常量量或者整数;
  2. 单个 case 中,可以出现多个结果选项, 使⽤用逗号分隔;
  3. 与 C 语⾔言等规则相反,Go 语⾔言不不需要⽤用break来明确退出⼀一个 case;
  4. 可以不不设定 switch 之后的条件表达式,在此种情况下,整个 switch 结
构与多个 if...else... 的逻辑作⽤用等同
1

# 数组和切⽚片

# 数组的声明

var a [3]int //声明并初始化为默认零值

a[0] = 1

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

c := [2][2]int{{1, 2}, {3, 4}} //多维数组初始化
1
2
3
4
5
6
7

# 数组元素遍历

func TestTravelArray(t *testing.T) {
  a := [...]int{1, 2, 3, 4, 5} //不不指定元素个数
  for idx/*索引*/, elem/*元素*/ := range a {
    fmt.Println(idx, elem)
  }
}
1
2
3
4
5
6

# 数组截取

a[开始索引(包含), 结束索引(不不包含)]

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
1
2
3
4
5
6

# 切⽚片内部结构

len: 元素的个数
cap: 内部数组的容量量
1
2

# 切⽚片声明

var s0 []int
s0 = append(s0, 1)
s := []int{}
s1 := []int{1, 2, 3}
s2 := make([]int, 2, 4)
/*[]type, len, cap
其中len个元素会被初始化为默认零值,未初始化元素不不可以访问
*/
1
2
3
4
5
6
7
8

切⽚片共享存储结构

# 数组 vs. 切⽚片

  1. 容量量是否可伸缩
  2. 是否可以进⾏行行⽐比较

# Map 基础

# Map 声明

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

m1 := map[string]int{}

m1["one"] = 1

m2 := make(map[string]int, 10 /*Initial Capacity*/)
1
2
3
4
5
6
7

# Map 元素的访问

在访问的 Key 不不存在时,仍会返回零值,不不能通过返回 nil 来判断元素是否存在

if v, ok := m["four"]; ok {
  t.Log("four", v)
} else {
  t.Log("Not existing")
}
1
2
3
4
5

# Map 遍历

m := map[string]int{"one": 1, "two": 2, "three": 3}
for k, v := range m {
  t.Log(k, v)
}
1
2
3
4

# Map 扩展

# Map 与⼯工⼚厂模式

  • Map 的 value 可以是⼀一个⽅方法^
  • 与 Go 的 Dock type 接⼝口⽅方式⼀一起,可以⽅方便便的实现单⼀一⽅方法对象的⼯工⼚厂模式

# 实现 Set

  1. 元素的唯⼀一性

  2. 基本操作

    1. 添加元素
    2. 判断元素是否存在
    3. 删除元素
    4. 元素个数

Go 的内置集合中没有 Set 实现, 可以 map[type]bool

# 字符串串与字符编码

# 字符串串

  1. string 是数据类型,不不是引⽤用或指针类型
  2. string 是只读的 byte slice,len 函数可以它所包含的 byte 数
  3. string 的 byte 数组可以存放任何数据

# Unicode UTF8

  1. Unicode 是⼀一种字符集(code point)
  2. UTF8 是 unicode 的存储实现 (转换为字节序列列的规则)

# 编码与存储

“中”

0x4E2D

0xE4B8AD

[0xE4,0xB8,0xAD]

字符

Unicode

UTF-8

string/[]byte
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 常⽤用字符串串函数

  1. strings 包 (https://golang.org/pkg/strings/)

  2. strconv 包 (https://golang.org/pkg/strconv/)

# 函数:⼀一等公⺠民

# 函数是⼀一等公⺠民

  1. 可以有多个返回值
  2. 所有参数都是值传递:slice,map,channel 会有传引⽤用的错觉
  3. 函数可以作为变量量的值
  4. 函数可以作为参数和返回值

与其他主要编程语⾔言的差异

# 学习函数式编程

# 函数:可变参数及 defer

# 可变参数

func sum(ops ...int) int {
  s := 0
  for _, op := range ops {
    s += op
  }
  return s
}
1
2
3
4
5
6
7

# defer 函数

func TestDefer(t *testing.T) {
  defer func() {
    t.Log("Clear resources")
  }()
  t.Log("Started")
  panic("Fatal error”) //defer仍会执⾏行行
        }
1
2
3
4
5
6
7

# ⾯面向对象编程

# Is Go an object-oriented language?

Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. Also, the lack of a type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java.

https://golang.org/doc/faq
1
2
3

# 封装数据和⾏行行为

# 结构体定义

type Employee struct {
  Id string
  Name string
  Age int
}
1
2
3
4
5

# 实例例创建及初始化

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"
1
2
3
4
5
6

# 行为(方法)定义

type Employee struct {
  Id string
  Name string
  Age int
}

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

//通常情况下为了了避免内存拷⻉贝我们使⽤用第⼆二种定义⽅方式
func (e *Employee) String() string {
  return fmt.Sprintf("ID:%s/Name:%s/Age:%d", e.Id, e.Name, e.Age)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 定义交互协议

# 接⼝口与依赖 Programmer.java

public interface Programmer {
  String WriteCodes() ;
}

GoProgrammer.java
public class GoProgrammer implements Programmer {
  @Override
  public String WriteCodes() {
    return "fmt.Println(\"Hello World\")";
  }
}

Task.java
public class Task{
  public static void main(String[] args) {
    Programmer prog = new GoProgrammer();
    String codes = prog.WriteCodes();
    System.out.println(codes);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Duck Type 式接⼝口实现

type Programmer interface {
  WriteHelloWorld() Code
}

type GoProgrammer struct {
}

func (p *GoProgrammer) WriteHelloWorld() Code {
  return "fmt.Println(\"Hello World!\")"
}
1
2
3
4
5
6
7
8
9
10

# Go 接口

  1. 接⼝为⾮入侵性,实现不依赖于借⼝定义
  2. 所以接口的定义可以包含在接口使用者包内

# 自定义类型

  1. type IntConvertionFn func(n int) int
  2. type MyPoint int

# 扩展与复⽤

# 复合

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

  • 与其他主要编程语言的差异

# 匿名类型嵌⼊

  1. 不支持子类替换
  2. ⼦类并不是真正继承了父类的⽅方法
    • ⽗类的定义的方法无法访问⼦类的数据和方法
    • 它 不是继承 ,如果我们把“内部 struct ”看作⽗类,把“外部 struct” 看作⼦类

# 多态与空接⼝口

# 多态

type Programmer interface {
  WriteHelloWorld() Code
}

type GoProgrammer struct {
}

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

type JavaProgrammer struct {
}
func (p *JavaProgrammer) WriteHelloWorld() Code {
  return "System.out.Println(\"Hello World!\")"
}

func writeFirstProgram(p Programmer) {
  fmt.Printf("%T %v\n", p, p.WriteHelloWorld())
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 空接⼝口与断⾔言

  1. 空接⼝口可以表示任何类型
  2. 通过断⾔言来将空接⼝口转换为制定类型
  3. v, ok := p.(int) //ok=true 时为转换成功

# 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 {
  ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 编写好的错误处理

# Go 的错误机制

  1. 没有异常机制
  2. error 类型实现了了 error 接⼝
  3. 可以通过 errors.New 来快速创建错误实例例
type error interface {
  Error() string
}
errors.New("n must be in the range [0,100]")
1
2
3
4

# 最佳实践

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

func TestGetFibonacci(t *testing.T) {
  var list []int
  list, err := GetFibonacci(-10)
  if err == LessThanTwoError {
    t.Error("Need a larger number")
  }
  if err == GreaterThanHundredError {
    t.Error("Need a larger number")
  }
  ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

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

# 最佳实践

# 及早失败,避免嵌套!

#

# panic 和 recover

# panic

  • panic ⽤用于不不可以恢复的错误
  • panic 退出前会执⾏行行 defer 指定的内容

# panic vs. os.Exit

  • os.Exit 退出时不不会调⽤用 defer 指定的函数
  • os.Exit 退出时不不输出当前调⽤用栈信息

# recover

Java
1
try{
  ...
}catch(Throwable t){
  
}
1
2
3
4
5

# recover

defer func() {
  if err := recover(); err != nil {
    //恢复错误
  }
}()
1
2
3
4
5

# 最常见的”错误恢复“

defer func() {
  if err := recover(); err != nil {
    log.Error(“recovered panic”,err)
  }
}()
1
2
3
4
5

# 当⼼心! recover 成为恶魔

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

# 构建可复用模块:包

# package

  1. 基本复用模块单元以⾸字⺟大写来表明可被包外代码访问

  2. 代码的 package 可以和所在的目录不一致

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

# package

  1. 通过 go get 来获取远程依赖
    • go get -u 强制从⽹络更新远程依赖
  2. 注意代码在 GitHub 上的组织形式,以适应 go get
    • 直接以代码路径开始,不要有 src
    • 示例例: https://github.com/easierway/concurrent_map

# init ⽅方法

  • 在 main 被执⾏前,所有依赖的 package 的 init 方法都会被执⾏。
  • 不同包的 init 函数按照包导入的依赖关系决定执行顺序。
  • 每个包可以有多个 init 函数。
  • 包的每个源⽂文件也可以有多个 init 函数,这点⽐比较特殊。

# 依赖管理理

# Go 未解决的依赖问题

  1. 同⼀一环境下,不不同项⽬目使⽤用同⼀包的不不同版本
  2. ⽆无法管理理对包的特定版本的依赖

# 协程机制

# Thead vs. Groutine

  1. 创建时默认的 stack 的⼤大⼩小
    • JDK5 以后 Java Thread stack 默认为1M^
    • Groutine 的 Stack 初始化⼤大⼩小为2K^
  2. 和 KSE (Kernel Space Entity) 的对应关系
    • Java Thread 是 1:1^
    • Groutine 是 M:N
M System Thread
P Processor
G Goroutine
1
2
3

# 共享内存并发机制

# Lock、Mutex、RWLock

package sync
Lock lock = ...;
lock.lock();
1
2
3

# WaitGroup

var wg sync.WaitGroup
for i := 0; i < 5000; i++ {
  wg.Add(1)
  go func() {
    defer func() {
      wg.Done()
    }()
    ...
  }()
}
wg.Wait()
1
2
3
4
5
6
7
8
9
10
11

# CSP 并发机制

# CSP vs. Actor

  • 和Actor的直接通讯不不同,CSP模式则是通过Channel进⾏行行通讯的,更更松耦合⼀一 些。
  • Go中channel是有容量量限制并且独⽴立于处理理Groutine,⽽而如Erlang,Actor模式 中的mailbox容量量是⽆无限的,接收进程也总是被动地处理理消息。

# Channel异步返回

private static FutureTask<String> service() {
FutureTask<String> task = new FutureTask<String>(()->"Do
something");
new Thread(task).start();
return task;
}

FutureTask<String> ret = service();
System.out.println("Do something else”);
System.out.println(ret.get());
1
2
3
4
5
6
7
8
9
10

# 多路路选择和超时控制

# select

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

select {
  case ret := <-retCh1:
  t.Logf("result %s", ret)
  case ret :=<-retCh2:
  t.Logf("result %s", ret)
  default:
  t.Error(“No one returned”)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# channel 的关闭和⼴广播

# channel 的关闭

  • 向关闭的 channel 发送数据,会导致 panic
  • v, ok <-ch; ok 为 bool 值,true 表示正常接受,false 表示通道关闭
  • 所有的 channel 接收者都会在 channel 关闭时,⽴立刻从阻塞等待中返回且上述 ok 值为 false。这个⼴广播机制常被利利⽤用,进⾏行行向多个订阅者同时发送信号。如:退出信号。

# 任务的取消

# 获取取消通知

func isCancelled(cancelChan chan struct{}) bool {
  select {
    case <-cancelChan:
    return true
    default:
    return false
  }
}
1
2
3
4
5
6
7
8

# 发送取消消息

func cancel_1(cancelChan chan struct{}) {
  cancelChan <- struct{}{}
}
1
2
3

# 通过关闭 Channel 取消

func cancel_2(cancelChan chan struct{}) {
  close(cancelChan)
}
1
2
3

# Context 与任务取消

# Context

  • 根 Context:通过 context.Background () 创建
  • 子 Context:context.WithCancel(parentContext) 创建
  • ctx, cancel := context.WithCancel(context.Background())
  • 当前 Context 被取消时,基于他的⼦ context 都会被取消
  • 接收取消通知 <-ctx.Done()

# 常⻅见并发任务

# 单例例模式 (懒汉式,线程安全)

public class Singleton {
  private static Singleton INSTANCE=null;
  private Singleton(){}
  public static Singleton getIntance(){
    if(INSTANCE==null){
      synchronized (Singleton.class){
        if(INSTANCE==null){
          INSTANCE = new Singleton();
        }
      }
    }
    return INSTANCE;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 单例例模式 (懒汉式,线程安全)

var once sync.Once
var obj *SingletonObj
func GetSingletonObj() *SingletonObj {
  once.Do(func() {
    fmt.Println("Create Singleton obj.")
    obj = &SingletonObj{}
  })
  return obj
}
1
2
3
4
5
6
7
8
9

# 对象池

使用buffered channel 实现对象池。

# sync.Pool 对象缓存

# sync.Pool 对象获取

  • 尝试从私有对象获取
  • 私有对象不不存在,尝试从当前 Processor 的共享池获取
  • 如果当前 Processor 共享池也是空的,那么就尝试去其他 Processor 的共享池获取
  • 如果所有⼦子池都是空的,最后就⽤用⽤用户指定的 New 函数产⽣生⼀一个新的对象返回

# sync.Pool 对象的放回

  • 如果私有对象不不存在则保存为私有对象^
  • 如果私有对象存在,放⼊入当前 Processor ⼦子池的共享池中 私有对象 共享池

# 使⽤用 sync.Pool

pool := &sync.Pool{
  New: func() interface{} {
    return 0
  },
}
arry := pool.Get().(int)
...
pool.Put(10)
1
2
3
4
5
6
7
8

# sync.Pool 对象的⽣生命周期

  • GC 会清除 sync.pool 缓存的对象
  • 对象的缓存有效期为下一次 GC 之前

# sync.Pool 总结

  • 适合于通过复⽤用,降低复杂对象的创建和 GC 代价
  • 协程安全,会有锁的开销
  • 生命周期受 GC 影响,不适合于做连接池等,需⾃己管理生命周期的资源的池化

# 测试

# 单元测试

# 内置单元测试框架

  • Fail, Error: 该测试失败,该测试继续,其他测试继续执⾏
  • FailNow, Fatal: 该测试失败,该测试中止,其他测试继续执⾏

# 内置单元测试框架

  • 代码覆盖率 go test -v - cover
  • 断⾔ https://github.com/stretchr/testify

# Benchmark

# Benchmark

func BenchmarkConcatStringByAdd(b *testing.B) {
  //与性能测试⽆无关的代码
  b.ResetTimer()
  for i := 0; i < b.N; i++ {
    //测试代码
  }
  b.StopTimer()
  //与性能测试⽆无关的代码
}
1
2
3
4
5
6
7
8
9

# Benchmark

go test -bench=. -benchmem

-bench=<相关benchmark测试>
Windows 下使⽤用 go test 命令⾏行行时,-bench=.应写为-bench="."
1
2
3
4
编辑
#Go
上次更新: 2020/08/23, 11:08:00
分布式概念

分布式概念→

最近更新
01
行为型-访问者模式
11-24
02
行为型-备忘录模式
11-24
03
行为型-命令模式
11-24
更多文章>
Theme by Vdoing | Copyright © 2019-2020 HoldDie | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式