Go笔记(十五):并发编程
Posted 无虑的小猪
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go笔记(十五):并发编程相关的知识,希望对你有一定的参考价值。
一、协程的创建
Go 语言支持并发,只需要通过 go 关键字来开启 goroutine(协程) 即可。
goroutine(协程) 是轻量级线程,goroutine(协程) 的调度是由 Golang 运行时进行管理的。
goroutine 语法格式(创建协程):
go 函数名( 参数列表 )
示例代码如下:
package main import ( "fmt" "time" ) func rest(msg string) for i := 0; i < 4; i++ fmt.Println(msg) time.Sleep(100 * time.Millisecond) func main() // 开启一个goroutine 协程运行 go rest(" go rest ") // main主线程运行 rest(" main rest ")
执行结果如下:
二、协程间的同步
2.1、WaitGroup
WaitGroup,和Java中的CountDownLatch实现原理相似,WaitGroup持有当前正在执行的协程数量,当某个协程执行完后,WaitGroup持有正在执行的协程数量减1,当WaitGroup中协程正执行的数量为0,wait()方法便不再阻塞。
示例代码如下:
package main import ( "fmt" "sync" ) var wg sync.WaitGroup func routine01(num int) // goroutine结束登记-1 defer wg.Done() fmt.Printf("goroutine runing, %v\\n", num) func main() for i := 0; i < 8; i++ // 启动一个goroutine就加1 wg.Add(1) go routine01(i) // 阻塞,等待所有登记的goroutine结束 wg.Wait()
执行结果如下:
2.2、Mutex互斥同步
package main import ( "fmt" "sync" "time" ) var m = 100 var n = 100 var lock sync.Mutex var wagp sync.WaitGroup // 增量函数 func add() defer wagp.Done() n += 1 time.Sleep(time.Millisecond * 8) // 减量函数 func sub() time.Sleep(time.Millisecond * 3) defer wagp.Done() n -= 1 // 未用mutex同步锁 func nomutex() for i := 0; i < 100; i++ go add() wagp.Add(1) go sub() wagp.Add(1) wagp.Wait() // 用mutex同步锁 func addMetux() defer wagp.Done() lock.Lock() m += 1 time.Sleep(time.Millisecond * 10) lock.Unlock() // 用mutex同步锁 func subMetux() defer wagp.Done() lock.Lock() time.Sleep(time.Millisecond * 3) m -= 1 lock.Unlock() // 使用mutex同步锁 func mutex01() for i := 0; i < 100; i++ go addMetux() wagp.Add(1) go subMetux() wagp.Add(1) wagp.Wait() func main() nomutex() mutex01() fmt.Printf("end n: %v\\n", n) fmt.Printf("end m: %v\\n", m)
执行结果如下:
mutex,和Java中的Lock实现原理相似。当执行lock()方法时,基于 CAS 操作,将Mutex的 state 设置为Metux为加锁状态,同时设置当前线程持有锁资源;执行Unlock()方法时,对state的值做调整,释放锁资源,唤醒等待线程。
三、协程管理相关包
协程管理在api-runtime的相关包下。
1、runtime.Gosched()
让出CPU时间片,重新等待安排任务。
package main import ( "fmt" "runtime" ) func show(msg string) for i := 0; i < 3; i++ fmt.Printf("msg: %v\\n", msg) func main() go show("php") // 主协程 for i := 0; i < 2; i++ // 让出cpu给子协程 runtime.Gosched() // 若此处注释,则子协程可能无法执行 fmt.Printf("%v\\n", "golang")
执行结果如下:
2、runtime.Goexit()
退出子协程。
示例如下:
package main import ( "fmt" "runtime" ) func show01(msg string) for i := 0; i < 8; i++ if i == 3 // 退出子协程 runtime.Goexit() fmt.Printf("msg: %v\\n", msg) func main() go show01("C") for i := 0; i < 1; i++ runtime.Gosched() fmt.Printf("%v\\n", "golang")
执行结果如下:
四、select
select是Go中并发编程的控制语句,用于处理异步IO操作。select会监听case语句中channel的读写操作,当channel为非阻塞状态(可读写),会触发select中的case。
1、select的语法结构
select case 读: // do something case 写: // do something default: // do something
若多个case都可运行,select随机选择一个执行,其他不会执行。没有可运行的case语句,有default语句,会执行default。
2、示例
package main import "fmt" // 创建通道 var chint = make(chan int, 5) var chstr = make(chan string, 5) func main() go func() // 关闭通道 defer close(chint) defer close(chstr) // 往通道里写数据 chint <- 10 chstr <- "chstr" () for i := 0; i < 10; i++ // 监听case语句中channel的读写操作,当channel为非阻塞状态(可读写),会触发select中的case select case i := <-chint: fmt.Printf("i: %v\\n", i) case s := <-chstr: fmt.Printf("s: %v\\n", s) default: fmt.Printf("default...\\n")
执行结果如下:
3、select注意事项
没有可运行的case语句,没有default语句,select会阻塞直到某个case通信可运行;
select中的case语句必须是一个channel操作;
select的default总是可运行的。
五、定时器Timer
定时器Timer只执行一个。
1、timer的创建
timer := time.NewTimer(time.Second) timer.C // 阻塞,直到指定时间到了
示例如下:
package main import ( "fmt" "sync" "time" ) var wait sync.WaitGroup // 使用定时器 func time01() defer wait.Done() fmt.Printf("before: %v\\n", time.Now()) timer := time.NewTimer(time.Second * 2) <-timer.C // 阻塞,直到指定时间到达 fmt.Printf("after: %v\\n", time.Now()) func main() wait.Add(1) go time01() // 同步等待协程执行完成 wait.Wait()
执行结果如下:
2、time.After
time.After,和Java中的Sleep功能相似,阻塞指定时间再运行。
示例如下:
package main import ( "fmt" "sync" "time" ) var wait sync.WaitGroup // 使用定时器 func time02() defer wait.Done() fmt.Printf("before: %v\\n", time.Now()) // 阻塞2s <-time.After(time.Second * 2) fmt.Printf("after: %v\\n", time.Now()) func main() wait.Add(1) go time02() // 同步等待协程执行完成 wait.Wait()
执行结果如下:
3、timer.stop
停止定时器事件。
示例如下:
package main import ( "fmt" "time" ) func main() // 创建定时器 timer := time.NewTimer(time.Second * 2) // 协程,匿名函数 go func() <-timer.C fmt.Println("timer func...") () // 停止定时器,阻止timer事件发生 stopFlag := timer.Stop() if stopFlag fmt.Println("timer stoped...")
执行结果如下:
4、timer.Reset
重置定时器。
示例如下:
package main import ( "fmt" "time" ) func main() // 重置定时器 reset fmt.Printf("before: %v\\n", time.Now()) timer := time.NewTimer(time.Second * 2) timer.Reset(time.Second * 1) <-timer.C fmt.Printf("after: %v\\n", time.Now())
执行结果如下:
六、周期执行器Ticker
与timer只执行一次相比,Ticker可周期执行。
示例如下:
package main import ( "fmt" "time" ) func main() // 创建周期执行器 ticker := time.NewTicker(time.Second * 2) // 周期执行 for _ = range ticker.C fmt.Printf("time: %v\\n", time.Now())
执行结果如下:
package main import ( "fmt" "time" ) func ticker() // 创建一个无缓冲的整型channel chint := make(chan int) // 关闭通道 defer close(chint) // 创建定时器 ticker := time.NewTicker(time.Second * 2) // 创建一个协程,利用周期定时器每2s向通道中写入数据 go func() for _ = range ticker.C select case chint <- 2: case chint <- 4: case chint <- 6: () sum := 0 // 遍历通道,若通道无数据,会阻塞 for v := range chint fmt.Printf("receive: %v\\n", v) sum += v if sum > 20 fmt.Printf("sum: %v\\n", sum) break func main() ticker()
执行结果如下:
七、atomic
atomic的原子操作可以保证任一时刻只有一个goroutine协程对变量做修改。
1、加减
package main import ( "fmt" "sync" "sync/atomic" ) var i int32 = 100 var waitg sync.WaitGroup // 原子加操作 func atomicAdd() atomic.AddInt32(&i, 1) waitg.Done() // 原子减操作 func atomicSub() atomic.AddInt32(&i, -1) waitg.Done() func main() for i := 0; i < 100; i++ // 子线程 waitg.Add(1) go atomicAdd() // 子线程 waitg.Add(1) go atomicSub() waitg.Wait() fmt.Printf("i = %v\\n", i)
执行结果如下:
2、载入 -> 读取操作、存储 -> 写入操作
package main import ( "fmt" "sync/atomic" ) // 载入与存储 func loadAndStore() var i int64 = 64 // 载入 -> 读取,原子操作 atomic.LoadInt64(&i) fmt.Printf("i: %v\\n", i) // 存储 -> 写入,原子操作 atomic.StoreInt64(&i, 128) fmt.Printf("i: %v\\n", i) func main() loadAndStore()
执行结果如下:
3、CAS -> 比较并交换
进行交换前变量的值未被修改,与参数old记录的值一致,满足此前提下才会进行交换。
package main import ( "fmt" "sync/atomic" ) // 比较和交换 func cas() var i int64 = 256 // 旧的值与变量i的值相同,则交换 result := atomic.CompareAndSwapInt64(&i, 256, 64) if result fmt.Println("cas success") else fmt.Println("cas fail") fmt.Printf("i: %v\\n", i) // 旧的值与变量i的值不同,则不交换 result01 := atomic.CompareAndSwapInt64(&i, 256, 8) if result01 fmt.Println("cas success") else fmt.Println("cas fail") fmt.Printf("i: %v\\n", i) func main() cas()
执行结果如下:
《go语言高级编程》笔记
1.6 常见并发模式
以上是关于Go笔记(十五):并发编程的主要内容,如果未能解决你的问题,请参考以下文章
go语言学习笔记 — 进阶 — 并发编程:轻量级线程goroutine —— 并发与并行
go语言学习笔记 — 进阶 — 并发编程:调整并发的运行性能(GOMAXPROCS)
go语言学习笔记 — 进阶 — 并发编程:为函数创建goroutine
go语言学习笔记 — 进阶 — 并发编程:同步sync,竞态检测 —— 检测代码在并发环境下出现的问题