前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go语言入门(八)线程安全&锁

Go语言入门(八)线程安全&锁

作者头像
alexhuiwang
发布2020-09-24 11:21:57
3450
发布2020-09-24 11:21:57
举报
文章被收录于专栏:运维博客运维博客

线程安全&锁

定时器&一次性定时器

  • 定时器
代码语言:javascript
复制
func main() {
   ticker := time.NewTicker(time.Second)
   //ticker.C是一个只读的chan,所以直接可以使用for range读取
   for v := range ticker.C {
      fmt.Printf("hello %v\n",v)   //按秒输出
   }
}
  • 一次性定时器
代码语言:javascript
复制
func main() {
   select {
   case <- time.After(time.Second):
      fmt.Printf("after\n")
   }
}
  • 超时控制
代码语言:javascript
复制
func queryDb(ch chan int) {
   //模拟DB查询
   time.Sleep(time.Second)  //模拟查询1秒
   ch <- 100
}

func main() {
   ch := make(chan int)
   go queryDb(ch)   //异步查询
   t := time.NewTicker(time.Second*3)    //定时3秒
   select {
   case v:= <- ch:
      fmt.Printf("result:%v\n",v)   //
   case <- t.C:
      fmt.Printf("timeout\n")
   }
}
  • 异常处理: 记录异常,不至于进程被panic等问题
代码语言:javascript
复制
func main() {
   go func() {
      // 使用recover()可以捕获goroutine的任何异常,从而不至于使得整个进程挂掉
      defer func() {
         err := recover()
         if err != nil {
            fmt.Printf("catch a panic,err:%v \n",err)
         }
      }()

      var p *int
      *p = 1000
      fmt.Printf("hello")
   }()
   var i int
   for {
      fmt.Printf("%d\n",i)
      time.Sleep(time.Second)
   }
}

线程安全

  • 典型的例子
    • 多个goroutine同时操守做一个资源,这个资源叫做临界区
    • 现实生活中的十字路口,通过红绿灯来实现线程安全
    • 火车上卫生间,通过互斥锁实现线程安全
  • 实际例子: x=x+1
    • 先从内存中取出x的值
    • CPU进行计算+1
    • 然后将x+1的结果放到内存

解决线程冲突问题

  • 以下代码的输出数据不是2000000
代码语言:javascript
复制
var count int

func test1(waitGroup *sync.WaitGroup) {
   for i:=0;i<1000000;i++ {
      count ++
   }
   waitGroup.Done()
}

func test2(waitGroup *sync.WaitGroup) {
   for i:=0;i<1000000;i++ {
      count ++
   }
   waitGroup.Done()
}

func main() {
   var wg sync.WaitGroup
   wg.Add(2)
   go test1(&wg)
   go test2(&wg)
   wg.Wait()
   fmt.Printf("count=%d\n",count)
}

互斥锁

  • 同时且只有一个线程进入临界区,其他的线程则在等待锁
  • 当互斥锁释放之后,等待锁的线程才可以获取锁进入临界区
  • 多个线程同时等待同一个锁,唤醒的策略是随机的

修复线程问题,使其正确输出

代码语言:javascript
复制
var count int
var mutex sync.Mutex

func test1(waitGroup *sync.WaitGroup) {
   for i:=0;i<1000000;i++ {
      mutex.Lock()
      count ++
      mutex.Unlock()
   }
   waitGroup.Done()
}

func test2(waitGroup *sync.WaitGroup) {
   for i:=0;i<1000000;i++ {
      //加锁
      mutex.Lock()
      count ++
      //解锁
      mutex.Unlock()
   }
   waitGroup.Done()
}

func main() {
   var wg sync.WaitGroup
   wg.Add(2)
   go test1(&wg)
   go test2(&wg)
   wg.Wait()
   fmt.Printf("count=%d\n",count)
}

读写锁

  • 使用场景: 读多写少的场景
  • 分为两种角色: 读锁和写锁
  • 当一个goroutine获取写锁之后,其他的goroutine获取写锁或读锁都会等待
  • 当一个goroutine获取读锁之后,其他的goroutine获取写锁都会等待,但其他goroutine获取读锁时,都会继续获得锁
代码语言:javascript
复制
var rwlock sync.RWMutex
var wg sync.WaitGroup
var counter int

func writer() {
   for i := 0;i<1000;i++ {
      rwlock.Lock()
      counter++
      rwlock.Unlock()
      time.Sleep(10*time.Millisecond)
   }
   wg.Done()
}

func reader()  {
   for i:=0;i<1000;i++ {
      rwlock.RLock()
      fmt.Printf("counter=%d\n",counter)
      time.Sleep(time.Millisecond)
      rwlock.RUnlock()
   }
   wg.Done()
}

func main() {
   wg.Add(1)
   go writer()
   for i:=0;i<10;i++ {
      wg.Add(1)
      go reader()
   }
   wg.Wait()
}
  • 对比读写锁与互斥锁的性能
代码语言:javascript
复制
var rwlock sync.RWMutex
var wg sync.WaitGroup
var mlock sync.Mutex
var counter int

func writer() {
   for i := 0;i<1000;i++ {
      rwlock.Lock()
      counter++
      rwlock.Unlock()
      time.Sleep(10*time.Millisecond)
   }
   wg.Done()
}

func reader()  {
   for i:=0;i<1000;i++ {
      rwlock.RLock()
      //fmt.Printf("counter=%d\n",counter)
      time.Sleep(time.Millisecond)
      rwlock.RUnlock()
   }
   wg.Done()
}

func writer_mutex() {
   for i := 0;i<1000;i++ {
      mlock.Lock()
      counter++
      mlock.Unlock()
      time.Sleep(10*time.Millisecond)
   }
   wg.Done()
}

func reader_mutex()  {
   for i:=0;i<1000;i++ {
      mlock.Lock()
      //fmt.Printf("counter=%d\n",counter)
      time.Sleep(time.Millisecond)
      mlock.Unlock()
   }
   wg.Done()
}

func main() {
   start := time.Now().UnixNano()
   wg.Add(1)
   go writer()
   for i:=0;i<10;i++ {
      wg.Add(1)
      go reader()
   }
   wg.Wait()
   end := time.Now().UnixNano()
   cost_time := (float64(end)-float64(start))/1000/1000/1000
   fmt.Printf("RWlock总耗时:%f s\n",cost_time)

   start_m := time.Now().UnixNano()
   wg.Add(1)
   go writer_mutex()
   for i:=0;i<10;i++ {
      wg.Add(1)
      go reader_mutex()
   }
   wg.Wait()
   end_m := time.Now().UnixNano()
   cost_time_m := (float64(end_m)-float64(start_m))/1000/1000/1000
   fmt.Printf("Mutexlock总耗时:%f s\n",cost_time_m)
}

原子操作

  • 计数需求,会使用到原子操作
  • 加锁代价会比较耗时,需要上下文切换
  • 针对基本数据类型,可以使用原子操作确保线程安全
  • 原子操作在用户态就可以完成,因此性能要比互斥锁要高
代码语言:javascript
复制
var counts int32

func test3() {
   for i:=0;i<1000000;i++ {
      //counts ++,替换成原子加1操作
      atomic.AddInt32(&counts,1)
   }
}

func test4() {
   for i:=0;i<1000000;i++ {
      atomic.AddInt32(&counts,1)
   }
}

func main() {
   go test3()
   go test4()
   time.Sleep(time.Second)
   fmt.Printf("counts=%d\n",counts)
}

练习

  • 采用多线程遍历目录,然后生成缩略图
  • 参考代码(单线程模式)
代码语言:javascript
复制
const DEFAULT_MAX_WIDTH float64 = 64
const DEFAULT_MAX_HEIGHT float64 = 64

// 计算图片缩放后的尺寸
func calculateRatioFit(srcWidth, srcHeight int) (int, int) {
   ratio := math.Min(DEFAULT_MAX_WIDTH/float64(srcWidth), DEFAULT_MAX_HEIGHT/float64(srcHeight))
   return int(math.Ceil(float64(srcWidth) * ratio)), int(math.Ceil(float64(srcHeight) * ratio))
}

// 生成缩略图
func makeThumbnail(imagePath, savePath string) error {

   file, _ := os.Open(imagePath)
   defer file.Close()

   img, _, err := image.Decode(file)
   if err != nil {
      return err
   }

   b := img.Bounds()
   width := b.Max.X
   height := b.Max.Y

   w, h := calculateRatioFit(width, height)

   fmt.Println("width = ", width, " height = ", height)
   fmt.Println("w = ", w, " h = ", h)

   // 调用resize库进行图片缩放
   m := resize.Resize(uint(w), uint(h), img, resize.Lanczos3)

   // 需要保存的文件
   imgfile, err := os.Create(savePath)
   if err != nil {
      fmt.Printf("os create file, err:%v\n", err)
      return err
   }
   defer imgfile.Close()

   // 以PNG格式保存文件
   err = png.Encode(imgfile, m)
   if err != nil {
      return err
   }

   return nil
}

func main() {
   var imageFile string
   var saveFile string

   flag.StringVar(&imageFile, "file", "", "--image file")
   flag.StringVar(&saveFile, "dest", "", "--dest file")
   flag.Parse()

   if len(imageFile) == 0 || len(saveFile) == 0 {
      fmt.Printf("%s -file image filename -dest dest filename\n", os.Args[0])
      return
   }

   err := makeThumbnail(imageFile, saveFile)
   if err != nil {
      fmt.Printf("make thumbnail failed, err:%v\n", err)
      return
   }
   fmt.Printf("make thumbnail succ, path:%v\n", saveFile)
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-03-31 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 线程安全&锁
    • 定时器&一次性定时器
      • 线程安全
        • 解决线程冲突问题
        • 互斥锁
        • 读写锁
      • 原子操作
        • 练习
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档