WaitGroup
结构与特点
type WaitGroup struct {
//隐藏字段或非导出字段
}
特点: 用于等待一组goroutine的结束,并且不关心并发操作的结果,或者有其他方式得到并发操作的结果。
方法
func (wg *WaitGroup) Add(delta int) //内部计数增加delta,delta可为负数。
//如果内部计数器小于0,方法会panic。
//一般在创建goroutine前执行,以保证在main goroutine调用Wait方法前计数器增加
func (wg *WaitGroup) Done() //将内部计数减1, 一般在goroutine最后执行,使用defer关键字
//应保证在Add方法之后调用
func (wg *WaitGroup) Wait() //Wait方法阻塞,直到内部计数器值为0
示例1
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("1st goroutine sleeping...")
time.Sleep(1)
}()
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("2st goroutine sleeping...")
time.Sleep(2)
}()
wg.Wait()
fmt.Println("All goroutines complete.")
}
示例2
package main
import (
"fmt"
"sync"
)
func hello(wg *sync.WaitGroup, id int) {
defer wg.Done()
fmt.Printf("Hello from %v!\n", id)
}
func main() {
var wg sync.WaitGroup
const numGreeters = 5
wg.Add(numGreeters)
for i := 0; i < numGreeters; i++ {
go hello(&wg, i+1)
}
wg.Wait()
}
Mutex与RWMutex
结构与特点
type Mutex struct {
//隐藏字段或非导出字段
}
type RWMutex struct {
//隐藏字段或非导出字段
}
特点: 互斥锁与读写锁,用于访问临界区时的同步控制。
方法
func (m *Mutex) Lock() //互斥锁加锁
func (m *Mutex) Unlock() //互斥锁解锁,对未锁定的锁解锁会导致运行时错误
func (rw *RWMutex) Lock() //读写锁加写锁
func (rw *RWMutex) Unlock() //读写锁解写锁,对未锁定的锁解锁会导致运行时错误
func (rw *RWMutex) RLock() //读写锁加读锁
func (rw *RWMutex) RUnlock() //读写锁解读锁,对未锁定的锁解锁会导致运行时错误
示例1
package main
import (
"fmt"
"sync"
)
func main() {
var count int
var lock sync.Mutex
increment := func() {
lock.Lock() //critical section加锁,这部分是性能的瓶颈
defer lock.Unlock() //保证panic时此操作也会执行,否则程序会死锁
count++
fmt.Printf("Incrementing: %d\n", count)
}
decrement := func() {
lock.Lock()
defer lock.Unlock()
count--
fmt.Printf("Decrementing: %d\n", count)
}
var arithmetic sync.WaitGroup
for i := 0; i < 5; i++ {
arithmetic.Add(1)
go func() {
defer arithmetic.Done()
increment()
}()
}
for i := 0; i < 5; i++ {
arithmetic.Add(1)
go func() {
defer arithmetic.Done()
decrement()
}()
}
arithmetic.Wait()
fmt.Println("Arithmetic complete.")
}
示例2
package main
import (
"fmt"
"math"
"os"
"sync"
"text/tabwriter"
"time"
)
func main() {
producer := func(wg *sync.WaitGroup, l sync.Locker) {
defer wg.Done()
for i := 5; i > 0; i-- {
l.Lock()
l.Unlock()
time.Sleep(1)
}
}
observer := func(wg *sync.WaitGroup, l sync.Locker) {
defer wg.Done()
l.Lock()
defer l.Unlock()
}
test := func(count int, mutex, rwMutex sync.Locker) time.Duration {
var wg sync.WaitGroup
wg.Add(count + 1)
beginTestTime := time.Now()
go producer(&wg, mutex) //获取写锁, 5轮
for i := count; i > 0; i-- { //获取读锁, count轮, 下文中比较了加读锁与加写锁的时间
go observer(&wg, rwMutex)
}
wg.Wait()
return time.Since(beginTestTime)
}
tw := tabwriter.NewWriter(os.Stdout, 0, 1, 2, ' ', 0) //输出格式参数
defer tw.Flush() //刷新输出
var m sync.RWMutex
fmt.Fprintf(tw, "Readers\tRWMutex\tMutex\n")
for i := 0; i < 18; i++ {
count := int(math.Pow(2, float64(i)))
fmt.Fprintf(
tw,
"%d\t%v\t%v\n",
count,
test(count, &m, m.RLocker()), //RWMutex的RLocker方法返回一个以RLock和RUnlock实现Lock接口的对象
test(count, &m, &m),
)
}
}
Once
结构与特点
type Once struct {
// 包含隐藏或非导出字段
}
特点:通过底层原语控制函数只执行一次。
方法
func (o *Once) Do(f func()) //Do方法当且仅当第一次调用时才会执行函数f,即使多次调用的参数f不同
示例1
package main
import (
"fmt"
"sync"
)
func main() {
var count int
increment := func() { count++ }
decrement := func() { count-- }
var once sync.Once
once.Do(increment)
once.Do(decrement)
fmt.Printf("Count: %d\n", count) //输出 Count: 1
}
示例2
package main
import "sync"
func main() {
var onceA, onceB sync.Once
var initB func()
initA := func() { onceB.Do(initB) }
initB = func() { onceA.Do(initA) }
onceA.Do(initA)
//此程序会导致死锁,然而不是因为Once内部问题而是逻辑问题导致的循环引用
}