0%

4.并发

goroutine

1
go fun(); //在函数前面加个go,即可开启一个goroutine
  • 程序启动时,只有一个goroutine来调用main函数,称之为主goroutine
  • 除返回或者进程结束外,go 没有提供其他方式结束goroutine

通道

表达式

chan [type],使用map构建,可以设置缓存容量(默认为0),零值为nil

1
2
3
4
5
6
ch1 := make(chan int,3)
ch2 = make(chan int) //无缓存
var out chan<- int //只写通道
var in <-chan int //只读通道
cap(ch1) //cap 获取容量
len(ch1) //获取元素数量

发送

若缓存满了,且无人接收,则阻塞

1
ch <- x	

接收

若无数据,则阻塞等待

1
2
3
4
5
6
7
x = <-ch
<-ch //接收,但丢弃结果,可以通过这个方法,实现C语言中,线程的wait
x,ok := <-ch //读取并判断是否已经关闭
// 持续读取,直到全部读完且通道被关闭
for x:= range ch{
//
}

关闭

关闭后发送会宕机,关闭后接收会获取所有值

1
close(ch)

select多路复用

多个等待条件满足时,选一个执行,被选中的几率相同,若均不满足且无default,则阻塞,

若有default 则为非阻塞处理,执行default中的语句

若通道为则nil,则永远不会被选中,则相关语句被禁用

1
2
3
4
5
select {
case <- ch1:
case x:= <- ch2:
default:
}

广播机制

不在通道上发送值,而是直接关闭它

竞态与互斥

并发:无法确定一个事件肯定先于另一个事件

互斥锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import "sync"	//所在包
var (
mu sync.Mutex //类型,和被保护对象写在一起,更有利于代码理解
i int
)
func func1(){
mu.Lock()
i++
mu.Unlock()
}
func func2(){
mu.Lock()
defer mu.Unlock() //通过defer 实现GO中的互斥锁自动释放
i++
}


读写锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import "sync"	//所在包
var (
rwmu sync.RWMutex //类型,和被保护对象写在一起,更有利于代码理解
i int
)
func func1(){
rwmu.RLock()
i++
rwmu.RUnlock()
}
func func2(){
rwmu.WLock()
defer rwmu.WUnlock() //通过defer 实现GO中的互斥锁自动释放
i++
}

内存同步

  • 编译器优化有可能导致指令乱序,若无在通道通信或者互斥操作这类同步原语,两条局部无关,全局有关的相邻的代码可能不被认为无关联关系,从而被优化,出现乱序现象
  • cache冲突:若无在通道通信或者互斥操作这类同步原语,CPU将不会cache中的数据刷回内存,从而导致访问变量时出现数据一致性问题
  • 全局变量需要用互斥,但解决同步互斥需要成本,所以尽可能把变量放在同一个goroutine中

单例

1
2
3
4
5
6
7
8
import "sync"	//所在包
var initOnce sync.Once
func init(){
//...
}
func func1(){
InitOnce.Do(init)
}

竞态检测器

go buildgo rungo test中加入参数-race,将记录在执行时对共享变量的所有访问

线程与goroutine

  • 栈空间大小:线程固定(2MB),goroutine动态(2KB-1GB)
  • 调度
    • 线程是全系统,goroutine是单个进程内部的
    • 线程有时间片,goroutine没有,但goroutine在休眠(互斥等待,time.Sleep)时,也会进行切换
  • GOMAXPROCS:go程序的线程数,默认为CPU数量,利用系统原语或者被嵌入的其他语言开辟的线程不计入其中。
  • goroutine没有句柄,go一直追求一种极简的风格,让任务简单化,避免通过句柄实现任务的全局控制,导致程序复杂化