这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天
goroutine
goroutine与OS线程的区别
- OS线程由OS内核来调试,控制成本从一个线程切换到另一个线程需要完整的上下文切换,上下文切换成本较高。
- Go调试器将使用一个称为m:n的调试技术,将m个goroutine调度到n个OS线程。
- OS线程调度由硬件时钟触发,而goroutine调试由Go语言结构触发
- OS线程有一个固定大小的栈内存(通常为2MB),goroutine的栈内存初始很小(通常为2KB),可以按需扩大和缩小。
goroutine的使用
启动一个goroutine
go func()
示例
并发地打印0~9
func print(i int) {
fmt.Println(i)
}
func main() {
for i := 0; i < 10; i++ {
go print(i)
}
time.Sleep(time.Second)
}
main函数运行完后,其它的goroutine会被强制结束。因此让main等待1秒再结束。
channel
channel是可以让一个goroutine发送特定特到另一个goroutine的一种机制,实现gorutine之间的通信。
不要通过共享内存来实现通信,而应该通过通信来共享内在。
channel的使用
创建channel
ch := make(chan int)创建一个元素类型是int的channel
发送语句
ch <- x向ch 发送一个元素x,goroutine会阻塞,直到其它goroutine从ch中读取了x.
接收语句
x = <-ch从ch读取一个元素,从过ch中没有元素,goroutine会阻塞,直到其它goroutine向ch发送元素。
关闭channel
close(ch) 关闭后的发送操作会导致宕机。接收操作将获取所有己发送的值,直到通道为空。
示例:
func output(ch chan int) {
for i := 0; i < 100; i++ {
ch <- i
time.Sleep(time.Second)
}
}
func main() {
ch := make(chan int)
go output(ch)
for {
x := <-ch
fmt.Println(x)
}
}
接收操作返回两个返回值,第一个是接收的元素,第二个是一个存尔值,它表示接收是否成功。如果通道己关闭且所有的元素都已经输出完,第二个返回值就为false
for {
x, ok := <-ch
if !ok {
break //通道关闭且读完,退出循环
}
}
也可以用range循环接收通道的元素,在接收完最后一个值后会自动退出循环
for x := range ch {
}
单向channel
chan<- int 是一个只能发送的channel
<-chan int是一个只能接收的channel
func output(ch chan<- int) {
for i := 0; i < 100; i++ {
ch <- i
time.Sleep(time.Second)
}
}
func print(ch <-chan int) {
for x := range ch {
fmt.Println(x)
}
}
func main() {
ch := make(chan int)
go output(ch)
go print(ch)
time.Sleep(time.Second * 20)
}
缓冲channel
带缓冲的channel有一个元素队列。向缓冲channel发送元素,不会阻塞goroutine等待接收,而是报元素放入缓冲队列。从channel接收元素是会获得缓冲队列的队首元素。
如果发送元素时缓冲队列已满,会阻塞等待元素出队。
如果读取元素时缓冲队列为空,会阻塞等等发送元素。
锁
竞态
竞态是指多个goroutine在按某些交错顺序执行时程序无法给出正确的结果。
数据竞态发生于两个goroutine并发地读写同一个变量且至少有一个是写入时。
互质锁sync.Mutex
Lock方法用于获取锁,Unlock方法用于释放锁。一个锁只能被一个goroutine。如果一个goroutine获取了锁,且没有释放。其它goroutine调用Lock方法时会阻塞,直到锁被释放它才能获取锁。锁一般用来保护共享变量,防止数据竞态的发生。在Lock和Unlock之间的代码可以自由地读写共享变量,不会被其它goroutine影响。
var (
x int64
lock sync.Mutex
)
func addWithLock() {
lock.Lock()
x += 1
lock.Unlock()
}
读写互斥锁sync.RWMutex
RWMutex可以调用RLock和RUlock方法分别来获取和释放一个读锁,读锁可以被多个goroutine同时获取。
可以调用Lock和Unlock方法来获取写锁,写锁只能被一个goroutine获取。
WaitGroup
sync.WaitGroup维护了一个计数器,
Add(delta int)计数器加delta
Done()计数器减1
Wait()阻塞直到计数器归零
func print(i int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println(i)
}
func main() {
wg := sync.WaitGroup{}
wg.Add(10)
for i := 0; i < 10; i++ {
go print(i, &wg)
}
wg.Wait()
fmt.Println("Done")
}