Go语言并发 | 青训营笔记

73 阅读3分钟

Go语言并发

1.并发与并行的区别

并发:多线程程序在一个核的CPU上运行,主要通过时间片的切换实现。

并行:多线程程序在多个核的CPU上运行,

广义上可以认为并行是实现并发的一种手段。

1.1Goroutine

image-20230603121241133.png

协程:用户态,轻量级线程,栈==KB==级别,创建和调度由go语言本身完成

线程:内核态,线程跑多个协程,栈**==MB==**级别

1.2 CSP(Communicating Sequential Processes)

Go语言提倡通过通信来共享内存,而不是通过共享内存实现通信.

此处go语言的通信就是通过channel实现的

1.3Channel

声明:make(chan 元素类型,[缓冲区大小])

根据缓冲区的有无,可将channel分为

  • 无缓冲channel:make(chan int),也被成为同步通道
  • 有缓冲channel:make(chan int , 2)

channel主要应用场景:

channel是 goroutine 与 goroutine 之间通信的重要桥梁,借助 channel,我们能很轻易的写出一个多协程通信程序。今天,我们就来看看这个 channel 的常用的应用场景。

  1. 数据交流:当作并发的buffer或者queue,解决生产者-消费者问题。多个goroutine可以并发当作生产者(Producer)和消费者(Consumer)。
  2. 数据传递:一个goroutine将数据交给另一个goroutine,相当于把数据的拥有权(引用)托付出去。
  3. 信号通知:一个goroutine可以将信号(closing、closed、data ready等)传递给另一个或者另一组goroutine 。
  4. 任务编排:可以让一组goroutine按照一定的顺序并发或者串行的执行,这就是编排的功能。
  5. :利用Channel也可以实现互斥锁的机制。
  6. 控制并发数
  7. 定时任务

1.4 并发安全 Lock

Lock锁机制,对共享资源进行操作前,先对资源加锁,然后进行操作,操作完成后释放锁,依次类推,在每次需要获取该共享资源时,都需要进行锁操作。

在go语言中,锁操作时要通过sync.Mutex实现的,

简单示例:

package main
import (
	"fmt"
	"sync"
	"time"
)
func main() {
	var lock sync.Mutex
	x := 0
	go func(x *int) {
		for i := 0; i < 10; i++ {
			lock.Lock()
			*x += 1
			lock.Unlock()
		}
	}(&x)
	x += 100
	time.Sleep(time.Second)
	fmt.Println(x)
}

优点:能够保证并发安全,

缺点:加锁和释放锁的过程比较耗费系统资源,同时如果操作不当,容易引发死锁

1.5 WaitGroup

WaitGroup是Golang应用开发过程中经常使用的并发控制技术。

WaitGroup,可理解为Wait-Goroutine-Group,即等待一组goroutine结束。比如某个goroutine需要等待其他几个goroutine全部完成,那么使用WaitGroup可以轻松实现。

下面程序展示了一个goroutine等待另外两个goroutine结束的例子:

package main
import (
    "fmt"
    "time"
    "sync"
)
func main() {
    var wg sync.WaitGroup
    wg.Add(2) //设置计数器,数值即为goroutine的个数
    go func() {
        //Do some work
        time.Sleep(1*time.Second)
        fmt.Println("Goroutine 1 finished!")
        wg.Done() //goroutine执行结束后将计数器减1
    }()
    go func() {
        //Do some work
        time.Sleep(2*time.Second)
        fmt.Println("Goroutine 2 finished!")
        wg.Done() //goroutine执行结束后将计数器减1
    }()
    wg.Wait() //主goroutine阻塞等待计数器变为0
    fmt.Printf("All Goroutine finished!")
}

简单的说,上面程序中wg内部维护了一个计数器:

  1. 启动goroutine前将计数器通过Add(2)将计数器==设置为待启动的goroutine个数。==
  2. 启动goroutine后,使用Wait()方法阻塞自己,等待计数器变为0。
  3. 每个goroutine执行结束通过Done()方法将计数器减1。
  4. 计数器变为0后,阻塞的goroutine被唤醒。

其实WaitGroup也可以实现一组goroutine等待另一组goroutine,这有点像玩杂技,很容易出错,如果不了解其实现原理更是如此。实际上,WaitGroup的实现源码非常简单。

总结

go语言通过goroutine实现并发,它是协程,一种轻量化的线程,是用户态,产生和调度由go语言本省处理。go语言实现并发访问控制的三种方式:channel,sync Mutex,WaitGroup