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
实现集合 Set
– value值为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])
unicode
和utf-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)
}
}
panic
和recover
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 ⽬录下查找
Go 协程机制
不同语言 创建线程的初始栈 大小对比:
Python
Java (JDK5+) Thread stack 默认为1M
Groutine Stack 初始化⼤⼩为2K (创建起来更快)
不同语言 thread (用户级别线程)
user space thread
与
KSE (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)
}