这是我参与「第五届青训营」伴学笔记创作活动的的第2天。
并发相关
Goroutines
Go语言中,每一个并发的执行单元叫作一个goroutine。新的goroutine会用go语句来创建,go语句是一个普通的函数或方法调用前加上关键字go。go语句会使其语句中的函数在一个新创建的goroutine中运行。如
go func(j int) {
hello(j)
}(i)
“顺序通信进程”CSP
Channels通道
Channels是goroutine之间的通信机制。如上图所示
使用内置的make函数,可以创建一个channel(第一个参数是channels的类型,第二个参数是缓存大小):
Ch1 := make(chan int) //无缓存
Ch1 := make(chan int,3) //有缓存
使用内置的close函数就可以关闭一个channel:
close(ch)
channel有发送和接受两个主要操作,发送和接收两个操作都使用<-运算符:
ch <- x
x = <-ch
<-ch
无缓存Channels的发送操作将导致发送者goroutine阻塞,直到另一个goroutine在相同的Channels上执行接收操作,当发送的值通过Channels成功传输之后,两个goroutine可以继续执行后面的语句。
带缓存的Channel内部持有一个元素队列,最大容量由make函数创建channel时第二个参数指定的。发送的数据会插入缓存,直到缓存满之前不会阻塞,接收操作从缓存中取数据,直到缓存为空之前不会阻塞。
并发安全
Sync包
sync包提供了基本的同步基元,如互斥锁。除了Once和WaitGroup类型,大部分都是适用于低水平程序线程。
Sync.Mutex
Mutex是一个互斥锁,可以创建为其他结构体的字段;零值为解锁状态。Mutex类型的锁和线程无关,可以由不同的线程加锁和解锁。如:
for i := 0; i < 2000; i++ {
lock.Lock()//加锁
x += 1 //临界区
lock.Unlock()//解锁
}
Sync.WaitGroup
WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束。
案例:
func HelloGoRoutine() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
}
因为当主线程结束时其他所有线程都将结束,所有使用time.Sleep(time.Second)让主线程等待其他线程完成,但如果一个线程的很大执行时间很长,很可能主线程休眠时间到了,其他的某个线程还没结束就被迫提前停止了,这种做法就不是很好了。
func ManyGoWait() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
使用sync.WaitGroup的wait()方法阻塞主线程直到所有线程结束,就能很好的解决子线程因为主线程的结束而被迫结束的问题。
补充:Sync.Once
Once是只执行一次动作的对象。
Do方法当且仅当第一次被调用时才执行函数f,如果once.Do(f)被多次调用,只有第一次调用会执行f。常可以用在单例模式之中。