一、golang之OOP(orient object programming)
在函数声明时, 在其名字之前放上一个变量, 即是一个方法。 这个附加的参数会将该函数附加到这种类型上, 即相当于为这种类型定义了一个独占的方法。(c++中称成员函数)
二、Goroutine和Channels
在golang中,每一个并发的执行单元叫做一个goroutine;而channel则是它们之间的通信机制,它可以让一个goroutine通过channel给另一个goroutine发送消息;需要注意的是,每个channel都有一个特殊的类型;
ch := make(chan int)
close(ch) //关闭channel
2.1 无buffer channel
ch := make(chan int) or ch := make(chan int , 0)
一个基于无缓存Channels的发送操作将导致发送者goroutine阻塞, 直到另一个goroutine在相同的Channels上执行接收操作, 当发送的值通过Channels成功传输之后, 两个goroutine可以继续执行后面的语句。 反之, 如果接收操作先发生, 那么接收者goroutine也将阻塞, 直
到有另一个goroutine在相同的Channels上执行发送操作。
基于无缓存Channels的发送和接收操作将导致两个goroutine做一次同步操作。 因为这个原因, 无缓存Channels有时候也被称为同步Channels。 当通过一个无缓存Channels发送数据时, 接收者收到数据发生在唤醒发送者goroutine之前;
2.2 单方向的channel
类型 chan<- int 表示一个只发送int的channel, 只能发送不能接收。 相反, 类型 <-chan int 表示一个只接收int的channel, 只能接收不能发送。 ( 箭头 <- 和关键字chan的相对位置表明了channel的方向。 ) 这种限制将在编译期检测。
1 12 // out 只发 channel 2 13 // in 只收channel 3 14 func squares(out chan<- int, in <-chan int) { 4 15 for v := range in { 5 16 out <- v * v 6 17 } 7 18 close(out) 8 19 }
2.3 带buffer的channel
ch = make(chan string, 3)
向缓存Channel的发送操作就是向内部缓存队列的尾部插入元素, 接收操作则是从队列的头部删除元素。 如果内部缓存队列是满的, 那么发送操作将阻塞直到因另一个goroutine执行接收操作而释放了新的队列空间。 相反, 如果channel是空的, 接收操作将阻塞直到有另一个
goroutine执行发送操作而向队列插入元素。
获取channel的容量:cap(ch) 获取channel数据的长度:len(ch)
三、select多路复用
1 package main 2 3 import ( 4 "fmt" 5 //"time" 6 ) 7 8 /* 9 select { 10 case <-ch1: 11 // ... 12 case <-ch2: 13 // ... 14 case <-ch3: 15 // ... 16 default: 17 // ... 18 } 19 */ 20 21 func main() { 22 j := make(chan int, 1) 23 //tick := time.Tick(1 * time.Second) 24 for down := 10; down > 0; down-- { 25 fmt.Println("down to here") 26 select { 27 case x := <-j: 28 fmt.Println(x) 29 case j <- down: 30 fmt.Println("put to j") 31 } 32 } 33 }
一、并发
一般情况下,我们没法知道分别位于两个goroutine事件x和y的执行顺序,x是在y之前还是之后还是同时发生是没法判断的。 当我们能够没有办法明确地确认一个事件是在另一个事件的前面或者后面发生的话, 就说明x和y这两个事件是并发的。
如何避免竞争?
由于其它的goroutine不能够直接访问变量, 它们只能使用一个channel来发送给指定的goroutine请求来查询更新变量。 这也就是Go的口头禅“不要使用共享数据来通信;使用通信来共享数据”。 一个提供对一个指定的变量通过cahnnel来请求
的goroutine叫做这个变量的监控(monitor)goroutine。
二、互斥
互斥原型实现:
1 var ( 2 sema = make(chan struct{}, 1) 3 balance int 4 ) 5 6 func Add(amount int) { 7 sema <- struct{}{} 8 balance = balance + amount 9 <-sema 10 } 11 12 func Get() int { 13 sema <- struct{}{} 14 b := balance 15 <-sema 16 } 17 18 import "sync" 19 var ( 20 mu sync.Mutex 21 balance int 22 ) 23 func Add(amount int){ 24 mu.Lock() 25 balance = balance + amount 26 mu.Unlock() 27 } 28 29 func Get() int { 30 mu.Lock() 31 b := balance 32 mu.Unlock() 33 }
func Get() int {
mu.Lock()
defer mu.Unlock()
b := balance
return b
}
2.1 读写锁
var rwmu sync.RWMutex ----用于多度少写
rwmu.Rlock() / rwmu.RUnlock() ----读锁定,此时只能进行读操作
rwmu.Lock() / rwmu.Unlock() ----读写锁定
2.2 sync.Once初始化
1 /* 2 sync.Once。 概念上来讲, 3 一次性的初始化需要一个互斥量mutex和一个boolean变量来记录初始化是不是已经完成了; 4 互斥量用来保护boolean变量和客户端数据结构。 Do这个唯一的方法需要接收初始化函数作为 5 其参数。 6 */ 7 var loadIconsOnce sync.Once 8 var icons map[string]int 9 func loadIcons() { 10 icons["first"] = 1 11 icons["second"] = 2 12 icons["thrid"] = 3 13 } 14 15 func Icon(name string) int { 16 loadIconsOnce.Do(loadIcons) 17 return icons[name] 18 }
2.3 sync.WaitGroup
先说说WaitGroup的用途:它能够一直等到所有的goroutine执行完成,并且阻塞主线程的执行,直到所有的goroutine执行完成。这里要注意一下,他们的执行结果是没有顺序的,调度器不能保证多个 goroutine 执行次序,且进程退出时不会等待它们结束。
WaitGroup总共有三个方法:Add(delta int),Done(),Wait()。简单的说一下这三个方法的作用。
Add:添加或者减少等待goroutine的数量
Done:相当于Add(-1)
Wait:执行阻塞,直到所有的WaitGroup数量变成0
如:
golang中的同步是通过sync.WaitGroup来实现的.WaitGroup的功能:它实现了一个类似队列的结构,可以一直向队列中添加任务,当任务完成后便从队列中删除,如果队列中的任务没有完全完成,可以通过Wait()函数来出发阻塞,防止程序继续进行,直到所有的队列任务都完成为止.
WaitGroup的特点是Wait()可以用来阻塞直到队列中的所有任务都完成时才解除阻塞,而不需要sleep一个固定的时间来等待.但是其缺点是无法指定固定的goroutine数目.可能通过使用channel解决此问题。