Golang接口 接口像是一个公司里面的领导,他会定义一些通用规范,只设计规范,而不实现规范。
go语言的接口,是一种新的类型定义 ,它把所有的具有共性的方法 定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
语法格式和方法非常类似。
接口的语法格式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type interface_name interface { method_name1 [return_type] method_name2 [return_type] method_name3 [return_type] ... method_namen [return_type] } type struct_name struct { } func (struct_name_variable struct_name) method_name1 () [return_type ] { } ... func (struct_name_variable struct_name) method_namen () [return_type ] { }
在接口定义中定义,若干个空方法。这些方法都具有通用性。
接口实例 下面我定义一个USB接口,有读read和写write两个方法,再定义一个电脑Computer和一个手机Mobile来实现这个接口。
USB接口
1 2 3 4 type USB interface { read() write() }
Computer结构体
1 2 type Computer struct {}
Mobile结构体
Computer实现USB接口方法
1 2 3 4 5 6 7 func (c Computer) read () { fmt.Println("computer read..." ) } func (c Computer) write () { fmt.Println("computer write..." ) }
Mobile实现USB接口方法
1 2 3 4 5 6 7 func (c Mobile) read () { fmt.Println("mobile read..." ) } func (c Mobile) write () { fmt.Println("mobile write..." ) }
测试
1 2 3 4 5 6 7 8 9 func main () { c := Computer{} m := Mobile{} c.read() c.write() m.read() m.write() }
运行结果
1 2 3 4 5 6 7 8 9 func main () { c := Computer{} m := Mobile{} c.read() c.write() m.read() m.write() }
实现接口必须实现接口中的所有方法 下面我们定义一个OpenClose接口,里面有两个方法open和close,定义个Door结构体,实现其中一个方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "fmt" type OpenClose interface { open() close () } type Door struct {} func (d Door) open () { fmt.Println("open door..." ) } func main () { var oc OpenClose oc = Door{} }
Golang接口值类型接收者和指针类型接收者 这个话题,本质上和方法的值类型接收者和指针类型接收者,的思考方法是一样的,值接收者是一个拷贝,是一个副本,而指针接收者,传递的是指针。
实例演示 定义一个Pet接口
1 2 3 type Pet interface { eat() }
定义一个Dog结构体
1 2 3 type Dog struct { name string }
实现Pet接口(接收者是值类型)
1 2 3 4 5 func (dog Dog) eat () { fmt.Printf("dog: %p\n" , &dog) fmt.Println("dog eat.." ) dog.name = "黑黑" }
测试
1 2 3 4 5 6 func main () { dog := Dog{name: "花花" } fmt.Printf("dog: %p\n" , &dog) dog.eat() fmt.Printf("dog: %v\n" , dog) }
运行结果
1 2 3 4 dog: 0xc000046240 dog: 0xc000046250 dog eat.. dog: {花花}
从运行结果,我们看出dog的地址变了,说明是复制了一份,dog的name没有变说明,外面的dog变量没有被改变。
将Pet接口改为指针接收者
1 2 3 4 5 func (dog *Dog) eat () { fmt.Printf("dog: %p\n" , dog) fmt.Println("dog eat.." ) dog.name = "黑黑" }
再测试
1 2 3 4 5 6 func main () { dog := &Dog{name: "花花" } fmt.Printf("dog: %p\n" , dog) dog.eat() fmt.Printf("dog: %v\n" , dog) }
运行结果
1 2 3 4 dog: 0xc00008c230 dog: 0xc00008c230 dog eat.. dog: &{黑黑}
Golang接口和类型的关系
一个类型可以实现多个接口
多个类型可以实现同一个接口(多态)
一个类型实现多个接口 一个类型实现多个接口,例如:有一个Player接口可以播放音乐,有一个Video接口可以播放视频,一个手机Mobile实现这两个接口,既可以播放音乐,又可以播放视频。
定义一个Player接口
1 2 3 type Player interface { playMusic() }
定义一个Video接口
1 2 3 type Video interface { playVideo() }
定义Mobile结构体
实现两个接口
1 2 3 4 5 6 7 func (m Mobile) playMusic () { fmt.Println("播放音乐" ) } func (m Mobile) playVideo () { fmt.Println("播放视频" ) }
测试
1 2 3 4 5 func main () { m := Mobile{} m.playMusic() m.playVideo() }
运行结果
多个类型实现同一个接口 比如,一个宠物接口Pet,猫类型Cat和狗类型Dog都可以实现该接口,都可以把猫和狗当宠物类型对待,这在其他语言中叫做多态 。
定义一个Pet接口
1 2 3 type Pet interface { eat() }
定义一个Dog结构体
定义一个Cat结构体
实现接口
1 2 3 4 5 6 7 func (cat Cat) eat () { fmt.Println("cat eat..." ) } func (dog Dog) eat () { fmt.Println("dog eat..." ) }
测试
1 2 3 4 5 6 7 func main () { var p Pet p = Cat{} p.eat() p = Dog{} p.eat() }
运行结果
Golang接口嵌套 接口可以通过嵌套,创建新的接口。例如:飞鱼,既可以飞,又可以游泳。我们创建一个飞Fly接口,创建一个游泳接口Swim,飞鱼接口有这两个接口组成。
飞Flyer接口
1 2 3 type Flyer interface { fly() }
创建Swimmer接口
1 2 3 type Swimmer interface { swim() }
组合一个接口FlyFish
1 2 3 4 type FlyFish interface { Flyer Swimmer }
创建一个结构体Fish
实现这个组合接口
1 2 3 4 5 6 7 func (fish Fish) fly () { fmt.Println("fly..." ) } func (fish Fish) swim () { fmt.Println("swim..." ) }
测试
1 2 3 4 5 6 func main () { var ff FlyFish ff = Fish{} ff.fly() ff.swim() }
运行结果
Golang 通过接口实现OCP设计原则 而面向对象的可复用设计 的第一块基石,便是所谓的”开-闭“原则(Open-Closed Principle,常缩写为OCP)。虽然,go不是面向对象语言,但是也可以模拟实现这个原则。对扩展 是开放的,对修改 是关闭的。
OCP设计原则实例 下面通过一个人养宠物的例子,来解释OCP设计原则。
定义一个宠物接口Pet
1 2 3 4 type Pet interface { eat() sleep() }
该接口有吃和睡两个方法。
定义个Dog结构体
1 2 3 4 type Dog struct { name string age int }
Dog实现接口方法
1 2 3 4 5 6 7 func (dog Dog) eat () { fmt.Println("dog eat..." ) } func (dog Dog) sleep () { fmt.Println("dog sleep..." ) }
定义一个Cat结构体
1 2 3 4 type Cat struct { name string age int }
Cat实现接口方法
1 2 3 4 5 6 7 func (cat Cat) eat () { fmt.Println("cat eat..." ) } func (cat Cat) sleep () { fmt.Println("cat sleep..." ) }
定义个Person结构体
1 2 3 type Person struct { name string }
为Person添加一个养宠物方法
1 2 3 4 func (per Person) care (pet Pet) { pet.eat() pet.sleep() }
最后测试一下
1 2 3 4 5 6 7 8 9 10 func main () { dog := Dog{} cat := Cat{} per := Person{} per.care(dog) per.care(cat) }
运行结果
1 2 3 4 dog eat... dog sleep... cat eat... cat sleep...
使用接口的这种设计方法,可以很好的解耦合代码,实现软件设计的OCP原则(即开闭原则)
这样设计,如果再添加一个宠物,例如:一个鸟 Bird
,原有的代码不用修改,直接添加就可以。
Golang继承 Golang本质上没有oop的概念,也没有继承的概念,但是可以通过结构体嵌套 实现这个特性。
例如
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 45 package mainimport "fmt" type Animal struct { name string age int } func (a Animal) eat () { fmt.Println("eat..." ) } func (a Animal) sleep () { fmt.Println("sleep" ) } type Dog struct { Animal } type Cat struct { Animal } func main () { dog := Dog{ Animal{ name: "dog" , age: 2 , }, } cat := Cat{ Animal{name: "cat" , age: 3 }, } dog.eat() dog.sleep() cat.eat() cat.sleep() }
Golang模拟OOP的属性和方法 golang没有面向对象的概念,也没有封装的概念,但是可以通过结构体 struct
和函数绑定 来实现OOP的属性和方法等特性。接收者 receiver 方法 。
例如 ,想要定义一个Person类,有name和age属性,有eat/sleep/work方法。
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 Person struct { name string age int } func (per Person) eat () { fmt.Println("eat..." ) } func (per Person) sleep () { fmt.Println("sleep..." ) } func (per Person) work () { fmt.Println("work..." ) } func main () { per := Person{ name: "tom" , age: 20 , } fmt.Printf("per: %v\n" , per) per.eat() per.sleep() per.work() }
Golang构造函数 Golang没有构造函数的概念,可以使用函数来模拟构造函数的的功能。
例如
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 package mainimport "fmt" type Person struct { name string age int } func NewPerson (name string , age int ) (*Person, error) { if name == "" { return nil , fmt.Errorf("name 不能为空" ) } if age < 0 { return nil , fmt.Errorf("age 不能小于0" ) } return &Person{name: name, age: age}, nil } func main () { person, err := NewPerson("tom" , 20 ) if err == nil { fmt.Printf("person: %v\n" , *person) } }
Golang包 包可以区分命令空间(一个文件夹中不能有两个同名文件),也可以更好的管理项目。go中创建一个包,一般是创建一个文件夹,在该文件夹里面的go文件中,使用package关键字声明包名称,通常,文件夹名称和包名称相同。并且,同一个文件下面只有一个包
创建包
创建一个名为dao的文件夹。
创建一个dao.go文件。
在该文件中声明包。1 2 3 4 5 6 package daoimport "fmt" func Test1 () { fmt.Println("test package" ) }
导入包 要使用某个包下面的变量或者方法,需要导入该包,导入包时,要导入从 GOPATH
开始的包路径,例如,在 service.go
中导入 dao
包
1 2 3 4 5 6 7 package mainimport "dao" func main () { dao.Test1() }
包注意事项
一个文件夹下只能有一个package import
后面的其实是 GOPATH
开始的相对目录路径,包括最后一段。但由于一个目录下只能有一个package,所以 import
一个路径就等于是 import
了这个路径下的包。
注意,这里指的是“直接包含”的go文件。如果有子目录,那么子目录的父目录是完全两个包。
比如你实现了一个计算器package,名叫 calc
,位于 calc
目录下;但又想给别人一个使用范例,于是在calc下可以建个example子目录(calc/example/),这个子目录里有个example.go(calc/example/example.go)。此时,example.go可以是main包,里面还可以有个main函数。
一个package的文件不能在多个文件夹下如果多个文件夹下有重名的package,它们其实是彼此无关的package。
如果一个go文件需要同时使用不同目录下的同名package,需要在 import
这些目录时为每个目录指定一个package的别名。
Golang 包管理工具go module Go module简介 go modules 是 golang 1.11 新加的特性,用来管理模块中包的依赖关系 。
Go mod 使用方法
初始化模块go mod init <项目模块名称>
依赖关系处理 ,根据go.mod文件go mod tidy
将依赖包复制到项目下的 vendor目录。go mod vendor
如果包被屏蔽(墙),可以使用这个命令,随后使用go build -mod=vendor编译
显示依赖关系go list -m all
显示详细依赖关系go list -m -json all
下载依赖go mod download [path@version]
[path@version]是非必写的
Golang并发编程之协程 Golang 中的并发是函数 相互独立运行的能力。Goroutines 是并发运行的函数。Golang 提供了 Goroutines 作为并发处理操作的一种方式。
创建一个协程非常简单,就是在一个任务函数前面添加一个go关键字:
实例1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport ( "fmt" "time" ) func show (msg string ) { for i := 1 ; i < 5 ; i++ { fmt.Printf("msg: %v\n" , msg) time.Sleep(time.Millisecond * 100 ) } } func main () { go show("java" ) show("golang" ) fmt.Println("end..." ) }
查看go
关键字去掉的运行效果
实例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 package mainimport ( "fmt" "io/ioutil" "log" "net/http" "time" ) func responseSize (url string ) { fmt.Println("Step1: " , url) response, err := http.Get(url) if err != nil { log.Fatal(err) } fmt.Println("Step2: " , url) defer response.Body.Close() fmt.Println("Step3: " , url) body, err := ioutil.ReadAll(response.Body) if err != nil { log.Fatal(err) } fmt.Println("Step4: " , len (body)) } func main () { go responseSize("https://www.xiaobaibk.com" ) go responseSize("https://baidu.com" ) go responseSize("https://jd.com" ) time.Sleep(10 * time.Second) }
Golang并发编程之通道channel Go 提供了一种称为通道的机制,用于在 goroutine 之间共享数据 。当您作为 goroutine 执行并发活动时,需要在 goroutine 之间共享资源或数据,通道充当 goroutine 之间的管道(管道)并提供一种机制来保证同步交换。
需要在声明通道时指定数据类型 。我们可以共享内置、命名、结构和引用类型的值和指针。数据在通道上传递:在任何给定时间只有一个 goroutine 可以访问数据项:因此按照设计不会发生数据竞争。
根据数据交换的行为,有两种类型的通道:无缓冲通道和缓冲通道。无缓冲通道用于执行 goroutine 之间的同步通信,而缓冲通道用于执行异步通信。无缓冲通道保证在发送和接收发生的瞬间执行两个 goroutine 之间的交换。缓冲通道没有这样的保证。
通道由 make 函数创建,该函数指定 chan 关键字和通道的元素类型。
这是创建无缓冲和缓冲通道的代码块: 语法 1 2 Unbuffered := make(chan int) buffered := make(chan int, 10 )
使用内置函数 make
创建无缓冲和缓冲通道。make
的第一个参数需要关键字 chan
,然后是通道允许交换的数据类型。
这是将值发送到通道的代码块需要使用 <-
运算符: 语法 1 2 goroutine1 := make(chan string, 5 ) goroutine1 <- "Australia"
一个包含 5 个值的缓冲区的字符串类型的 goroutine1 通道。然后我们通过通道发送字符串“Australia”。
这是从通道接收值的代码块: 语法 1 data := <-goroutine1 // 从通道接收字符串
<-
运算符附加到通道变量(goroutine1)的左侧,以接收来自通道的值。
无缓冲通道 在无缓冲通道中,在接收到任何值之前没有能力保存它。在这种类型的通道中,发送和接收 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 package main import ( "fmt" "math/rand" "time" ) var values = make(chan int)func send ( ) { rand.Seed(time.Now().UnixNano()) value := rand.Intn(10 ) fmt.Printf("send: %v\n" , value) values <- value } func main ( ) { defer close(values) go send() fmt.Println("wait..." ) value := <-values fmt.Printf("receive: %v\n", value) fmt.Println("end...") }
Golang并发编程之WaitGroup实现同步 实例演示
查看添加 WaitGroup
和不添加 WaitGroup
的区别
1 2 3 4 5 6 7 8 9 10 11 12 13 var wg sync.WaitGroup func 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() }
Golang并发编程之runtime包 runtime包里面定义了一些协程管理相关的api
runtime.Gosched() 让出CPU时间片,重新等待安排任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport ( "fmt" "runtime" ) func show (s string ) { for i := 0 ; i < 2 ; i++ { fmt.Println(s) } } func main () { go show("java" ) for i := 0 ; i < 2 ; i++ { runtime.Gosched() fmt.Println("golang" ) } }
runtime.Goexit() 退出当前协程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( "fmt" "runtime" "time" ) func show () { for i := 0 ; i < 10 ; i++ { if i >= 5 { runtime.Goexit() } fmt.Printf("i: %v\n" , i) } } func main () { go show() time.Sleep(time.Second) }
runtime.GOMAXPROCS 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 package mainimport ( "fmt" "runtime" "time" ) func a () { for i := 1 ; i < 10 ; i++ { fmt.Println("A:" , i) } } func b () { for i := 1 ; i < 10 ; i++ { fmt.Println("B:" , i) } } func main () { fmt.Printf("runtime.NumCPU(): %v\n" , runtime.NumCPU()) runtime.GOMAXPROCS(2 ) go a() go b() time.Sleep(time.Second) }
Golang并发编程之Mutex互斥锁实现同步 除了使用channel实现同步之外,还可以使用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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package mainimport ( "fmt" "sync" "time" ) var m int = 100 var lock sync.Mutexvar wt sync.WaitGroupfunc add () { defer wt.Done() lock.Lock() m += 1 time.Sleep(time.Millisecond * 10 ) lock.Unlock() } func sub () { defer wt.Done() lock.Lock() time.Sleep(time.Millisecond * 2 ) m -= 1 lock.Unlock() } func main () { for i := 0 ; i < 100 ; i++ { go add() wt.Add(1 ) go sub() wt.Add(1 ) } wt.Wait() fmt.Printf("m: %v\n" , m) }
Golang并发编程之channel的遍历 方法1 for循环+if判断 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" func main () { c := make (chan int ) go func () { for i := 0 ; i < 10 ; i++ { c <- i } close (c) }() for { if data, ok := <-c; ok { fmt.Printf("data: %v\n" , data) } else { break } } }
方法2 for range 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package main import "fmt" func main() { c := make(chan int) go func() { for i := 0; i < 10; i++ { c <- i } close(c) }() for v := range c { fmt.Printf("v: %v\n", v) } }
注意:如果通道关闭,读多写少,没有了就是默认值,例如,int 就是0,如果没有关闭就会死锁。
Golang并发编程之select
select是Go中的一个控制结构,类似于switch
语句,用于处理异步IO操作。select
会监听case语句中channel的读写操作,当case中channel读写操作为非阻塞状态(即能读写)时,将会触发相应的动作。
select中的case语句必须是一个channel操作
select中的default子句总是可运行的。
如果有多个case
都可以运行,select
会随机公平地选出一个执行,其他不会执行。
如果没有可运行的case
语句,且有default
语句,那么就会执行default
的动作。
如果没有可运行的case
语句,且没有default
语句,select
将阻塞,直到某个case
通信可以运行
实例 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" "time" ) var chanInt = make (chan int )var chanStr = make (chan string )func main () { go func () { chanInt <- 100 chanStr <- "hello" close (chanInt) close (chanStr) }() for { select { case r := <-chanInt: fmt.Printf("chanInt: %v\n" , r) case r := <-chanStr: fmt.Printf("chanStr: %v\n" , r) default : fmt.Println("default..." ) } time.Sleep(time.Second) } }
Golang并发编程之Timer Timer顾名思义,就是定时器的意思,可以实现一些定时操作,内部也是通过channel来实现的。
实例演示
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 package mainimport ( "fmt" "time" ) func main () { timer1 := time.NewTimer(time.Second * 2 ) t1 := time.Now() fmt.Printf("t1:%v\n" , t1) t2 := <-timer1.C fmt.Printf("t2:%v\n" , t2) timer2 := time.NewTimer(time.Second * 2 ) <-timer2.C fmt.Println("2s后" ) time.Sleep(time.Second * 2 ) fmt.Println("再一次2s后" ) <-time.After(time.Second * 2 ) fmt.Println("再再一次2s后" ) timer3 := time.NewTimer(time.Second) go func () { <-timer3.C fmt.Println("Timer 3 expired" ) }() stop := timer3.Stop() if stop { fmt.Println("Timer 3 stopped" ) } fmt.Println("before" ) timer4 := time.NewTimer(time.Second * 5 ) timer4.Reset(time.Second * 1 ) <-timer4.C fmt.Println("after" ) }
Golang并发编程之Ticker Timer只执行一次,Ticker可以周期的执行。
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport ( "fmt" "time" ) func main () { ticker := time.NewTicker(time.Second) counter := 1 for _ = range ticker.C { fmt.Println("ticker 1" ) counter++ if counter >= 5 { break } } ticker.Stop() }
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 package mainimport ( "fmt" "time" ) func main () { chanInt := make (chan int ) ticker := time.NewTicker(time.Second) go func () { for _ = range ticker.C { select { case chanInt <- 1 : case chanInt <- 2 : case chanInt <- 3 : } } }() sum := 0 for v := range chanInt { fmt.Printf("接收: %v\n" , v) sum += v if sum >= 10 { fmt.Printf("sum: %v\n" , sum) break } } }
Golang并发编程之原子变量的引入 先看一个实例
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" "sync" "time" ) var i = 100 var lock sync.Mutexfunc add () { lock.Lock() i++ lock.Unlock() } func sub () { lock.Lock() i-- lock.Unlock() } func main () { for i := 0 ; i < 100 ; i++ { go add() go sub() } time.Sleep(time.Second * 3 ) fmt.Printf("i: %v\n" , i) }
这是我们之前的写法,使用锁实现协程的同步
下面使用原子操作
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 package mainimport ( "fmt" "sync/atomic" "time" ) var i int32 = 100 func add () { atomic.AddInt32(&i, 1 ) } func sub () { atomic.AddInt32(&i, -1 ) } func main () { for i := 0 ; i < 100 ; i++ { go add() go sub() } time.Sleep(time.Second * 3 ) fmt.Printf("i: %v\n" , i) }
Golang并发编程之原子操作详解 atomic 提供的原子操作能够确保任一时刻只有一个goroutine对变量进行操作,善用 atomic 能够避免程序中出现大量的锁操作。
atomic常见操作有:
增减
载入 read
比较并交换 cas
交换
存储 write
下面将分别介绍这些操作。
增减操作 atomic 包中提供了如下以Add为前缀的增减操作:
1 2 3 4 5 - func AddInt32 (addr *int32 , delta int32 ) (new int32 ) - func AddInt64 (addr *int64 , delta int64 ) (new int64 ) - func AddUint32 (addr *uint32 , delta uint32 ) (new uint32 ) - func AddUint64 (addr *uint64 , delta uint64 ) (new uint64 ) - func AddUintptr (addr *uintptr , delta uintptr ) (new uintptr )
载入操作 atomic 包中提供了如下以Load为前缀的增减操作:
1 2 3 4 5 6 - func LoadInt32 (addr *int32 ) (val int32 ) - func LoadInt64 (addr *int64 ) (val int64 ) - func LoadPointer (addr *unsafe.Pointer) (val unsafe.Pointer) - func LoadUint32 (addr *uint32 ) (val uint32 ) - func LoadUint64 (addr *uint64 ) (val uint64 ) - func LoadUintptr (addr *uintptr ) (val uintptr )
载入操作能够保证原子的读变量的值,当读取的时候,任何其他CPU操作都无法对该变量进行读写,其实现机制受到底层硬件的支持。
比较并交换 该操作简称 CAS(Compare And Swap)。 这类操作的前缀为 CompareAndSwap
:
1 2 3 4 5 6 - func CompareAndSwapInt32 (addr *int32 , old, new int32 ) (swapped bool ) - func CompareAndSwapInt64 (addr *int64 , old, new int64 ) (swapped bool ) - func CompareAndSwapPointer (addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool ) - func CompareAndSwapUint32 (addr *uint32 , old, new uint32 ) (swapped bool ) - func CompareAndSwapUint64 (addr *uint64 , old, new uint64 ) (swapped bool ) - func CompareAndSwapUintptr (addr *uintptr , old, new uintptr ) (swapped bool )
该操作在进行交换前首先确保变量的值未被更改,即仍然保持参数 old
所记录的值,满足此前提下才进行交换操作。CAS的做法类似操作数据库时常见的乐观锁机制。
交换 此类操作的前缀为 Swap
:
1 2 3 4 5 6 - func SwapInt32 (addr *int32 , new int32 ) (old int32 ) - func SwapInt64 (addr *int64 , new int64 ) (old int64 ) - func SwapPointer (addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer) - func SwapUint32 (addr *uint32 , new uint32 ) (old uint32 ) - func SwapUint64 (addr *uint64 , new uint64 ) (old uint64 ) - func SwapUintptr (addr *uintptr , new uintptr ) (old uintptr )
相对于CAS,明显此类操作更为暴力直接,并不管变量的旧值是否被改变,直接赋予新值然后返回背替换的值。
存储 此类操作的前缀为 Store
:
1 2 3 4 5 6 - func StoreInt32 (addr *int32 , val int32 ) - func StoreInt64 (addr *int64 , val int64 ) - func StorePointer (addr *unsafe.Pointer, val unsafe.Pointer) - func StoreUint32 (addr *uint32 , val uint32 ) - func StoreUint64 (addr *uint64 , val uint64 ) - func StoreUintptr (addr *uintptr , val uintptr )
此类操作确保了写变量的原子性,避免其他操作读到了修改变量过程中的脏数据。