您的位置:首页 > 编程语言 > Go语言

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

2020-03-31 20:10 645 查看

线程安全&锁

定时器&一次性定时器

  • 定时器
func main() {
ticker := time.NewTicker(time.Second)
//ticker.C是一个只读的chan,所以直接可以使用for range读取
for v := range ticker.C {
fmt.Printf("hello %v\n",v)   //按秒输出
}
}
  • 一次性定时器
func main() {
select {
case <- time.After(time.Second):
fmt.Printf("after\n")
}
}
  • 超时控制
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等问题
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
    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)
    }

    互斥锁

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

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

    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获取读锁时,都会继续获得锁
    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()
    }
    • 对比读写锁与互斥锁的性能
    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)
    }

    原子操作

    • 计数需求,会使用到原子操作
    • 加锁代价会比较耗时,需要上下文切换
    • 针对基本数据类型,可以使用原子操作确保线程安全
    • 原子操作在用户态就可以完成,因此性能要比互斥锁要高
    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)
    }

    练习

    • 采用多线程遍历目录,然后生成缩略图
    • 参考代码(单线程模式)
    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)
    }
  • 内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: