互斥锁用法详解(Go 快速入门指南 – 互斥锁和定时器)
互斥锁
对于任一共享资源 ,同一时间保证只有一个操作者 ,这种方法称为互斥机制 。
关键字Mutex表示互斥锁类型 ,它的Lock方法用于获取锁 ,Unlock方法用于释放锁 。在Lock和Unlock之间的代码 ,可以读取和修改共享资源 ,这部分区域称为临界区 。
错误的并发操作
先来看一个错误的示例 。
在Map小节中讲到 ,Map不是并发安全的 ,也就是说 ,如果在多个线程中,同时对一个 Map 进行读写 ,会报错 。现在来验证一下 , 通过启动100 个 goroutine来模拟并发调用,每个 goroutine 都对 Map 的 key 进行设置 。
packagemain import"sync" funcmain(){ m:=make(map[int]bool) varwgsync.WaitGroup forj:=0;j<100;j++{ wg.Add(1) gofunc(keyint){ deferfunc(){ wg.Done() }() m[key]=true//对Map进行并发写入 }(j) } wg.Wait() } //$gorunmain.go //输出如下 ,报错信息 /** fatalerror:concurrentmapwrites fatalerror:concurrentmapwrites goroutine104[running]: main.main.func1(0x0?) /home/codes/Go-examples-for-beginners/main.go:18+0x66 createdbymain.main /home/codes/Go-examples-for-beginners/main.go:13+0x45 goroutine1[semacquire]: sync.runtime_Semacquire(0xc0000112c0?) /usr/local/go/src/runtime/sema.go:62+0x25 sync.(*WaitGroup).Wait(0x60?) /usr/local/go/src/sync/waitgroup.go:139+0x52 main.main() /home/codes/Go-examples-for-beginners/main.go:22+0x105 ... ... ... */通过输出信息fatal error: concurrent map writes可以看到 ,并发写入 Map 确实会报错 。
正确的并发操作
Map 并发写入如何正确地实现呢?
一种简单的方案是在并发临界区域 (也就是设置 Map key 的地方) 进行加互斥锁操作,互斥锁保证了同一时刻 只有一个 goroutine 获得锁 ,其他 goroutine 全部处于等待状态 ,这样就把并发写入变成了串行写入 , 从而消除了报错问题 。
packagemain import( "fmt" "sync" ) funcmain(){ varmusync.Mutex m:=make(map[int]bool) varwgsync.WaitGroup forj:=0;j<100;j++{ wg.Add(1) gofunc(keyint){ deferfunc(){ wg.Done() }() mu.Lock()//写入前加锁 m[key]=true//对Map进行并发写入 mu.Unlock()//写入完成解锁 }(j) } wg.Wait() fmt.Printf("Mapsize=%d\n",len(m)) } //$gorunmain.go //输出如下 /** Mapsize=100 */超时控制
利用channel (通道)和time.After()方法实现超时控制 。
例子
packagemain import( "fmt" "time" ) funcmain(){ ch:=make(chanbool) gofunc(){ deferfunc(){ ch<-true }() time.Sleep(2*time.Second)//模拟超时操作 }() select{ case<-ch: fmt.Println("ok") case<-time.After(time.Second): fmt.Println("timeout!") } } //$gorunmain.go //输出如下 /** timeout! */定时器
调用time.NewTicker方法即可。
例子
packagemain import( "fmt" "time" ) funcmain(){ ticker:=time.NewTicker(time.Second) deferticker.Stop() done:=make(chanbool) gofunc(){ time.Sleep(5*time.Second)//模拟耗时操作 done<-true }() for{ select{ case<-done: fmt.Println("Done!") return case<-ticker.C: fmt.Println(time.Now().Format("2006-01-0215:04:05")) } } } //$gorunmain.go //输出如下 ,你的输出可能和这里的不一样 /** 2021-01-0315:40:21 2021-01-0315:40:22 2021-01-0315:40:23 2021-01-0315:40:24 2021-01-0315:40:25 Done! */扩展阅读
1.互斥锁 - 维基百科 (https://zh.wikipedia.org/wiki/互斥锁)
2.临界区 - 百度百科 (https://baike.baidu.com/item/临界区/8942134)
联系我
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!