Go并发编程指南第一篇
竞争条件和数据竞争
当两个或多个操作必须以正确的顺序执行时,就会出现竞争条件,但程序尚未编写为保证维护此顺序。
数据竞争是指一个并发操作试图读取一个变量,而在某个不确定的时间另一个并发操作试图写入同一个变量。main func 是主 goroutine。 func main() { var data int go func() { data++ }() if data == 0 { fmt.Printf("the value is %d", data) } }sync内存访问同步
sync 包包含对低级内存访问同步最有用的并发原语。临界区是代码中可以访问共享内存的地方mutex互斥体
Mutex 代表"互斥",是一种保护程序关键部分的方法。 type Counter struct { mu sync.Mutex value int } func (c *Counter) Increment() { c.mu.Lock() defer c.mu.Unlock() c.value++ }waitgroup等待组
调用添加一组goroutines var wg sync.WaitGroup for _, salutation := range []string{"hello", "greetings", "good day"} { wg.Add(1) go func(salutation string) { defer wg.Done() fmt.Println(salutation) }(salutation) } wg.Wait()读写互斥锁
更细粒度的内存控制,可以请求只读锁 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) for i := count; i > 0; i-- { 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 RWMutex Mutex ") for i := 0; i < 20; i++ { count := int(math.Pow(2, float64(i))) fmt.Fprintf( tw, "%d %v %v ", count, test(count, &m, m.RLocker()), test(count, &m, &m), ) }cond条件
如果有某种方法可以让 goroutine 有效地休眠,直到收到唤醒并检查其状态的信号,那就更好了。这正是 Cond 类型为我们所做的。
Cond 和 Broadcast 是用于通知在 Wait 调用中阻塞的 goroutines 条件已被触发的方法。 type Button struct { Clicked *sync.Cond } func main() { button := Button{ Clicked: sync.NewCond(&sync.Mutex{}), } // running on goroutine every function that passed/registered // and wait, not exit until that goroutine is confirmed to be running subscribe := func(c *sync.Cond, param string, fn func(s string)) { var goroutineRunning sync.WaitGroup goroutineRunning.Add(1) go func(p string) { goroutineRunning.Done() c.L.Lock() // critical section defer c.L.Unlock() fmt.Println("Registered and wait ... ") c.Wait() fn(p) }(param) goroutineRunning.Wait() } var clickRegistered sync.WaitGroup for _, v := range []string{ "Maximizing window.", "Displaying annoying dialog box!", "Mouse clicked."} { clickRegistered.Add(1) subscribe(button.Clicked, v, func(s string) { fmt.Println(s) clickRegistered.Done() }) } button.Clicked.Broadcast() clickRegistered.Wait() }Once
确保即使在多个 goroutine 之间也只执行一次 var count int increment := func() { count++ } var once sync.Once var increments sync.WaitGroup increments.Add(100) for i := 0; i < 100; i++ { go func() { defer increments.Done() once.Do(increment) }() } increments.Wait() fmt.Printf("Count is %d ", count)Pool
管理连接池,数量 package main import ( "fmt" "sync" ) func main() { myPool := &sync.Pool{ New: func() interface{} { fmt.Println("Creating new instance.") return struct{}{} }, } // Get call New function defined in pool if there is no instance started myPool.Get() instance := myPool.Get() fmt.Println("instance", instance) // here we put a previously retrieved instance back into the pool, // this increases the number of instances available to one myPool.Put(instance) // when this call is executed, we will reuse the // previously allocated instance and put it back in the pool myPool.Get() var numCalcsCreated int calcPool := &sync.Pool{ New: func() interface{} { fmt.Println("new calc pool") numCalcsCreated += 1 mem := make([]byte, 1024) return &mem }, } fmt.Println("calcPool.New", calcPool.New()) calcPool.Put(calcPool.New()) calcPool.Put(calcPool.New()) calcPool.Put(calcPool.New()) calcPool.Put(calcPool.New()) calcPool.Get() const numWorkers = 1024 * 1024 var wg sync.WaitGroup wg.Add(numWorkers) for i := numWorkers; i > 0; i-- { go func() { defer wg.Done() mem := calcPool.Get().(*[]byte) defer calcPool.Put(mem) // Assume something interesting, but quick is being done with // this memory. }() } wg.Wait() fmt.Printf("%d calculators were created.", numCalcsCreated) }死锁
死锁是其中所有并发进程都在等待彼此。 package main import ( "fmt" "sync" "time" ) type value struct { mu sync.Mutex value int } func main() { var wg sync.WaitGroup printSum := func(v1, v2 *value) { defer wg.Done() v1.mu.Lock() defer v1.mu.Unlock() // deadlock time.Sleep(2 * time.Second) v2.mu.Lock() defer v2.mu.Unlock() fmt.Printf("sum=%v ", v1.value+v2.value) } var a, b value wg.Add(2) go printSum(&a, &b) go printSum(&b, &a) wg.Wait() }
总结
本文是go语言并发编程指南最佳实践第一篇,后续第二篇还会整理各种channel的特性,锁的使用在并发编程中的特点与各种用途举例,全部都是最接地气的代码示例。关注我,敬请期待下一篇。