参考书:Go语言编程
第四章 并发编程
1 Go语言支持协程goroutine
补充:
1.1 如果在一个函数调用前加上go关键字,即在新的goroutine中并发执行。如果这个函数有返回值,这个返回值会被丢弃。
1.2 当main( )执行完即程序退出,不会等待goroutine执行完毕。
2 协程的简单例子
var counter int = 0
func Count(lock *sync.Mutex){
lock.Lock() // 加锁
counter ++
fmt.Println(counter)
lock.Unlock() // 解锁
}
func main(){
lock := &sync.Mutex{}
for i := 0;i<10;i++{
go Count(lock)
}
for{
lock.Lock()
c := counter
lock.Unlock()
runtime.Gosched() // 让出CPU时间片
if c >= 10{
break
}
}
}
3 Go语言的通信方式channel,channel可以在两个或多个goroutine之间传递消息。
4 channel是类型相关的,一个channel只能传递一种类型的值,这个类型需要在声明channel时指定。
5 channel的一般声明形式为var chanName chan ElementType,ElementType为元素类型,如下
var ch chan int
var m map[string] chan bool // 这个是声明一个map,元素是bool型的channel
6 channel的定义也可使用make( ),如下
ch := make(chan int) // 声明并初始化了一个int类型的名为ch的channel
7 channel写入和读出。向channel写入数据通常会导致程序阻塞,直到有其他goroutine从这个channel中读取数据。如果channel之前没有写入数据,那么从channel读取数据也会导致程序阻塞,直到channel被写入数据。
ch <- value // 将value值写入ch
value := <- ch // 读取ch值赋值给value
8 select语句,用于监听IO操作,当IO操作执行时会触发。每个case语句都必须是一个面向channel的操作,如下
select {
case <- chan1 :
// 如果chan1成功读到数据则进行该case处理语句,如果失败则继续执行下一个case
case chan2 <- 2 :
// 如果成功向chan2写入数据则进行该case处理语句
default :
// 如果上面都没有执行成功则进入default处理流程
}
9 select语句的语法
- 每个case都必须是一个通信
- 如果有多个case都可以运行,select会随机选择一个执行,其他的不会执行
- 如果没有case可执行,则去执行default,如果没有default,则会阻塞,直到有个case可以执行
10 创建一个带缓冲的channel
c := make(chan int , 1024) // 创建了一个大小为1024的int类型channel,即使没有读取方,写入方也可以一直往channel里写入,在缓冲区被填完之前都不会阻塞
for i := range c { // 可以直接使用range关键字从channel读取,而不需要用 <-
fmt.Println("Received: ", i )
}
11 channel的超时机制,常见的是利用select机制,例如
timeout := make(chan bool,1)
go func(){
time.Sleep(1e9)
timeout <- true
}()
select {
case <- ch:
fmt.Println("ch")
case <- timeout:
fmt.Println("timeout")
}
补充:channel的传递例子
type PipeData struct {
value int
handler func(int) int
next chan int
}
func handle(queue chan *PipeData){
for data := range queue {
data.next <- data.handler(data.value)
}
}
12 channel是一个原生类型,不仅支持被channel传递,也支持类型转换。单向channel只能用于发送或接收数据。
var ch1 chan int // 不是单向channel
var ch2 chan<- float64 // 单向channel,只用于写float64数据
var ch3 <-chan int // 单向channel,只用于读取int数据
ch4 := make(chan int)
ch5 := <-chan int(ch4) // 类型转换,强制转换为一个单向的读取channel
ch6 := chan<- int(ch4) // 类型转换,强制转换为一个单向的写入channel
ch7 := make(chan<- int) // 创建单向channel
ch8 := make(<-chan int) // 创建单向channel
ch9 := chan int(ch8) // errror , 不能将单向强制转换为双向
用法如下
func Parse( ch <-chan int) { // 限制只能读取channel
for value := range ch {
fmt.Println("Parsing value :" , value)
}
}
13 关闭channel,用内置函数close()即可,如close(ch),如果要判断一个channel是否已经关闭,可以使用多重返回值,如下
x , ok := <-ch
14 同步锁,Go语言的sync包提供了两种锁类型:sync.Mutex和sync.RWMutex。Mutex是简单的锁,当goroutine获取Mutex后其他goroutine只能等这个goroutine释放了该Mutex。RWMutex是单写多读锁,在读锁占用时会阻止写,但不阻止读,其他goroutine可以获取读锁(调用RLock())。而写锁(调用Lock())会阻止其他goroutine读和写的所有操作
补充:
1 可以在每个goroutine中控制何时主动出让时间片给其他goroutine,这可以使用runtime.Goshced( ) 实现。
2 Go语言包中的sync包提供两种锁类型:sync.Mutex和sync.RWMutex。Mutex是最简单的一种锁,当一个goroutine获取后,其他goroutine只能等待它释放Mutex。RWMutex是单写多读模型。在读锁占用时,会阻止其他goroutine写但不阻止读。在写锁占用时,会阻止其他goroutine写读。
15 对于这两种锁类型,任何一个Lock()或RLock()均需要保证对应有Unlock()或RUnlock()调用与之对应,否则可能导致等待该锁的所有goroutine处于饥饿状态,甚至可能导致死锁。
var l sync.Mutex
func foo() {
l.Lock()
defer l.Unlock()
// toDo
}
16 全局唯一性操作,即只需要运行一遍代码,如全局初始化操作,Go语言提供了一个Once类型来实现
var a string
var once sync.Once
func setup(){
a = "hello,world"
}
func doprint(){
once.Do(setup)
fmt.Println(a)
}
func twoprint(){
go doprint()
go doprint()
}
once的Do()可以保证在全局范围内只调用指定函数一次,即setup( ),而且其他goroutine在调用此语句时会先被阻塞,直至全局中once.Do( )执行完才能继续