[并发相关|青训营笔记]

43 阅读3分钟

这是我参与「第五届青训营」伴学笔记创作活动的的第2天。

并发相关

Goroutines

Go语言中,每一个并发的执行单元叫作一个goroutine。新的goroutine会用go语句来创建,go语句是一个普通的函数或方法调用前加上关键字go。go语句会使其语句中的函数在一个新创建的goroutine中运行。如

go func(j int) {
    hello(j)
}(i)

“顺序通信进程”CSP

image.png

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。常可以用在单例模式之中。