传统的并发控制在Go的标准库中也是有提供的,而且使用起来也非常简单。但是需要注意的是,这些传统的控制手段在使用的时候,同样会面临传统并发编程中会遇到的所有挑战。
对于这些传统的并发控制手段,主要都是由标准库中的sync包提供的。sync包中提供的主要类型有Locker、Cond、Map、Mutex、Once、Pool、RWMutex和WaitGroup。其中可能有不少类型的名字听起来都十分的熟悉,这里只选其中比较常用的几个来记录。
Locker
Locker是sync包中提供的一个接口,主要用来表示提供了Lock()和Unlock()方法的可以用来在并发编程中执行锁的功能的对象,例如sync包中提供的Mutex类型、RWmutex类型等。
Once
Once是一个可以被多次调用但是只会执行一次的对象,不论每次传入的参数是否相同,Once就是只会执行一次。例如以下这个示例。
|
|
Once对象也适合用来创建单例对象使用。
Mutex与RWMutex
Mutex是经典的互斥锁实现,互斥锁在刚被创建的时候是未锁闭状态,而且在使用的时候,一定注意要使用引用的形式(指针)传递互斥锁的实例,否则互斥锁将失去其并发控制功能。Mutex类型实现了Locker接口,其提供的方法主要可以实现以下功能。
Lock(),这是Locker接口规定必须要实现的功能,当调用Lock()的时候,会尝试获取锁并锁闭,如果不能成功获取到锁并锁闭,那么将会阻塞当前的协程直到成功获取到锁为止。TryLock(),用于尝试获取锁,但是在未获取到锁的时候,并不会阻塞当前的协程,而是会发回一个false返回值。Unlock(),解锁并释放当前获取到的锁。互斥锁被解锁并释放以后,就可以被其他的协程所获取并锁闭。
在大部分并发编程中,并不推荐直接使用Mutex这种偏底层的并发控制。
从Mutex类型引申出来的类型是RWMutex,即读写锁,主要用于单写多读的情况下。因为大部分的资源在并发条件下,只有写操作是互斥的(影响值的一致性),但是读操作是不互斥的。所以读写锁提供了写锁和读锁两种锁,对应的也提供了两种锁的操作方法。
Lock()和Unlock()用于对写操作进行加锁和解锁,调用Lock()会导致读和写都被锁闭。RLock()和RUnlock()用于对读操作进行加锁和解锁,调用RLock()会导致写操作被锁闭,但是不会影响其他的协程继续调用RLock()。RUnlock()只会解锁一次RLock(),不会影响其他剩余的RLock()调用。资源必须在所有的RLock()都被解锁以后,才能进行写操作。
RUnlock()的次数多于调用RLock()的次数,程序将会panic。如果多次锁闭但是不进行解锁,将会导致死锁,所以在使用的时候,锁闭与解锁的操作必须是对称的,并且是可以抵达的。
以下是RWMutex的一个简单使用示例。
|
|
Cond
Cond是创建一个条件变量来对协程进行控制,所有受控的协程都会集结在这个条件变量的位置上等待调度。举例来说就是多个协程在等待,一个协程在发送通知事件,这种情况通常出现在多个协程在等待资源准备就绪的场景中,
Cond的核心是一个Locker类型的字段,这个Locker类型的字段被用来在各个协程中的完成控制操作,这个Locker类型的字段通常都是使用Mutex类型或者RWMutex类型的实例来充当。Cond提供了以下几个方法来进行协程的控制。
Broadcast()用于唤醒所有等待Cond变量的协程。Signal()用于只唤醒一个正在等待Cond变量的协程。Wait()用于挂起调用协程,让调用Wait()的协程等待Broadcast()或者Signal()。
Wait()挂起当前协程的时候,需要先调用Cond实例中Locker对象的锁,这个锁是用来锁闭协程中所使用到的共享资源的。也就是说在使用Wait()之前,必须先锁闭Cond实例。
Wait()的时候Cond中的锁会阻止通知协程对于共享资源的访问。在调用Wait()的时候,Cond中的所将会自动解锁,当Wait()被Broadcast()或者Signal()结束而返回的时候,会自动再次对Cond中的锁进行锁闭。
以下是一个使用Cond进行协程控制的简单示例。
|
|
WaitGroup
WaitGroup直译过来就是“等待组”,它的功能就是用来等待一组协程的结束。WaitGroup的使用其实很简单,只需要在主协程启动子协程之前调用Add()来调整目前需要等待的子协程数量,然后在子协程执行结束以后调用Done(),然后再在需要让主协程停下来的地方调用Wait()即可。用一个示例说明就是下面这样。
|
|