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的值
解决线程冲突问题
- 以下代码的输出数据不是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) }
相关文章推荐
- Go语言入门3-数组
- go 语言入门笔记
- Go语言入门——Hello World
- Go语言入门自学宝典005-fmt包的格式化输出输入
- Golang(Go 语言)入门学习-7-方法
- Go 语言官方教程-学习指南-快速入门
- Go语言基础入门--函数,错误处理
- Go语言入门之for循环
- [Go语言]一、入门Hello,World
- go语言1小时——从不会到入门
- go语言快速入门:Web开发框架(10)
- 不一样的go语言之入门篇-Hello World
- Go语言笔记一——基础概念以及入门
- go语言入门-搞定cmd
- go语言快速入门:项目构建实践(21)
- Go语言入门之数组
- 2019最新GO语言零基础从入门到精通WEB编程名库讲解全套视频教程
- go语言入门(一)
- go语言快速入门:模板应用(14)
- go语言入门3 变量