GO的并发之Channel、select多路复用、并发安全和锁 | 青训营;

91 阅读5分钟

channel

go语言采用的并发模型是CSP(communicating sequential process),提倡通过通信共享内存而不是通过内存共享而实现通道。

go语言中的channel就是goroutine的连接通道,使得goroutine发送特定值到另一个goroutine中。

go语言的channel是一种特殊的类型.我们可以将其理解为一个队列,遵循着先入先出的规则。每一个通道都有一个具体类型的导管,当我们在声明这条通道时也需要为其指定元素类型。

channel声明

var 变量名称 chan 元素类型
var c1 chan int //声明一条传递整数的通道
var c2 chan string //声明一条传递字符串的通道
var c3 chan []int //声明一条传递int切片的通道

channel零值

当我们声明通道类型后,并未对其进行初始化,则它的默认零值为nil

var ch chan int
fmt.Println(ch) //nil

初始化channel

我们可以使用make对通道进行初始化。

make(chan 元素类型, [缓冲大小])
c1 := make(chan int) 
c2 := make(chan bool, 1)

channel操作

通道拥有发送、接收和关闭三种操作。发送和接收都需要使用<-符号来完成。

这里我们需要注意,通道值是可以被垃圾回收机制回收掉的。通道通常由发送方进行关闭操作,并且当接收方明确等待通道关闭的信号时才需要关闭操作。并且对于关闭的通道,我们不能再对其发送值,否则会导致panic;进行接收则会一直获取值直到通道为空为止。当对一个关闭且没有值的通道进行执行接收操作则会得到对应类型的零值。关闭一个已经关闭的通道会导致panic。

ch := make(chan int) //发送 
ch <- 10 //将10发送到ch中 //接收 
x := <- ch //从ch中接收值并赋值给x 
<-ch//从ch中接收值,忽略结果
fmt.Println(x) //10 
//关闭 
close(ch)

无缓冲通道

无缓冲通道也叫做阻塞的通道。 我们可以看见,这段代码在执行的时候会引起deadlock的错误,deadlock表示goroutine都被挂起导致死锁。我们可以理解为,无缓冲的通道只有在接收方能够接收值的时候才可以发送,否则就会一直处于等待发送的阶段。同样的,当我们对一个无缓冲通道执行接收操作时,如果没有向通道发送值的操作时也会导致阻塞。我们可以通过增加一个接收方来解决上述问题。 我们可以看见,使用无缓冲通道可以使发送和接收的goroutine同步化,因此无缓冲通道也称为同步通道

有缓存通道

有缓存通道,顾名思义,就是我们在初始化时给它一个容量,只要通道内的元素小于等于该通道的容量时,它就不会引起错误,而那些发送接收的行为也会一直在通道内等待,等待另一个通道去接收或者发送。

单向通道

go语言中的通道如果我们只想让它进行接收或者发送,那么该怎么做呢?go语言提供了单向通道来处理我们这种需求。

select多路复用

当我们需要对多条通道进行处理时,我们可以使用select来完成对通道的处理。select的使用方式与switch非常类似,它们都有case分支和一个default默认分支。每个case都对应一个接收或者发送过程,select会一直等待,直到它的某个case完成对应的操作。select可以一次处理多个channel的发送和接收操作。如果同时有多个case被满足,那么select会随机选择一个case去执行。

并发安全和锁

通过goroutine的学习后大家会发现,经常会出现多个goroutine处理同一个数据的情况发生。 通过上述代码我们可以发现,x的结果会出现6187,3476等情况,这就是因为两个goroutine同时对一个数据进行处理时,就会引起这个情况。我们换一种理解,因为goroutine是并发处理,那么当第一个goroutine执行第3000次的时候,第二个goroutine正好在这个时候执行第2500次,而当前x的值是5500,那么它们同时对5500自增,第一个goroutine自增后的值是5501,第二个goroutine自增后的值也是5501。因此就会导致结果与预期结果不符。

互斥锁

go语言中提供了一种互斥锁,常用于控制共享资源的访问。它能保证在同一时间只有一个goroutine在访问共享资源。

sync.Mutex

方法名功能
func (m *Mutex) Lock()获取互斥锁
func (m *Mutex) Unlock()释放互斥锁

读写互斥锁

当我们在使用互斥锁的时候,我们会发现,互斥锁使我们的效率有所降低。当我们去读取一个资源的时候,没有必要为其增加互斥锁,只有当我们需要修改一个资源的时候,为了避免重复操作,我们才需要增加互斥锁。那么这个时候,我们就需要用到读写锁。

sync.RWMutex

方法名功能
func (rw *RWMutex) Lock()获取写锁
func (rw *RWMutex) UnLock()释放写锁
func (rw *RWMutex) RLock()获取读锁
func (rw *RWMutex) RUnLock()释放读锁
func (rw *RWMutex) RLock()返回一个实现Locker接口的读写锁

sync.WaitGroup

go语言中的sync.WaitGroup可以用于实现并发任务的同步操作。

方法名功能
func (wg * WaitGroup) Add(num int)计数器+num
(wg *WaitGroup) Done()计数器-1
(wg *WaitGroup) Wait()阻塞直到全部计数器归0

sync.Once

当我们需要某些操作在高并发的场景下必须执行一次时,我们就可以使用到sync.Once。例如配置文件的加载。