Go 并发编程指南
当两个或多个操作必须以正确的顺序执行时,就会出现竞争条件,但程序尚未编写为保证维护此顺序。
数据竞争是指一个并发操作试图读取一个变量,而在某个不确定的时间另一个并发操作试图写入同一个变量。main func 是主 goroutine。
func main() {
var data int
go func() {
data++
}()
if data == 0 {
fmt.Printf("the value is %d", data)
}
}
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等待组
调用添加一组goroutinesvar 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\tRWMutex\tMutex\n")
for i := 0; i < 20; i++ {
count := int(math.Pow(2, float64(i)))
fmt.Fprintf(
tw,
"%d\t%v\t%v\n",
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\n", 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\n", v1.value+v2.value)
}
var a, b value
wg.Add(2)
go printSum(&a, &b)
go printSum(&b, &a)
wg.Wait()
}
总结
本文是go语言并发编程指南最佳实践第一篇,后续第二篇还会整理各种channel的特性,锁的使用在并发编程中的特点与各种用途举例,全部都是最接地气的代码示例。关注我,敬请期待下一篇。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。