go并发编程
1waitgroup
WaitGroup就是等待所有的goroutine全部执行完毕,add方式和Down方法要配套使用
package main
import (
"fmt"
"sync"
)
func main() {
var wq sync.WaitGroup
wq.Add(100) //监控多少个goroutine执行结束
for i:= 0;i<100;i++ {
//开启一个协程
go func(i int) {
defer wq.Done() //和add是一起使用的
fmt.Println(i)
}(i)
}
wq.Wait() //等待所有的goroutine结束
}
2通过锁来完成全局变量的原子性操作
开启两个gorountine对total进行相同此时的加减,但是这一段程序的运行结果每一次都不一样
资源竞争,加锁
package main
import (
"fmt"
"sync"
)
var total int
var wg sync.WaitGroup
func main() {
wg.Add(2)
go add()
go sub()
wg.Wait()
fmt.Println(total)
}
func add() {
defer wg.Done()
for i := 0; i < 100000; i++ {
total += 1
}
}
func sub() {
defer wg.Done()
for i := 0; i < 100000; i++ {
total -= 1
}
}
加锁之后代码成功运行
package main
import (
"fmt"
"sync"
)
var total int
var wg sync.WaitGroup
var lock sync.Mutex
func main() {
wg.Add(2)
go add()
go sub()
wg.Wait()
fmt.Println(total)
}
func add() {
defer wg.Done()
for i := 0; i < 100000; i++ {
lock.Lock()
total += 1
lock.Unlock()
}
}
func sub() {
defer wg.Done()
for i := 0; i < 100000; i++ {
lock.Lock()
total -= 1
lock.Unlock()
}
}
锁不能复制。
更加优雅的方式,使用golang的原子包
package main
import (
"fmt"
"sync"
"sync/atomic"
)
var total int64
var wg sync.WaitGroup
//var lock sync.Mutex
func main() {
wg.Add(2)
go add()
go sub()
wg.Wait()
fmt.Println(total)
}
func add() {
defer wg.Done()
for i := 0; i < 100000; i++ {
atomic.AddInt64(&total, 1) //原子性的操作
}
}
func sub() {
defer wg.Done()
for i := 0; i < 100000; i++ {
atomic.AddInt64(&total,-1)
}
}
lock相对atomic性能较差,lock基于操作系统调度
3读写锁
锁实际上是将并行的代码串行化了,使用lock肯定影响性能,即使是设计所,也应该尽量保证并行
4goroutine进行通讯
不要通过共享内存来通讯,而要通过通讯来实现内存共享
channel的基础用法
package main
import "fmt"
func main() {
// 名字 类型 存储类型
var msg chan string //默认值未nil
msg = make(chan string ,1) //channel的初始化的值如果是0的话,放值进去会阻塞,如果设为0就为无缓冲channel
msg <- "大大怪" //将右边的值放在channel中
name := <- msg //将channel中的值取出来给name
fmt.Println(name)
}
无缓冲channel用法
package main
import (
"fmt"
"time"
)
func main() {
// 名字 类型 存储类型
var msg chan string //默认值未nil
msg = make(chan string, 0) //channel的初始化的值如果是0的话,放值进去会阻塞,如果设为0就为无缓冲channel
go func(msg chan string) {
name := <-msg //将channel中的值取出来给name
fmt.Println(name)
}(msg)
msg <- "大大怪" //将右边的值放在channel中
time.Sleep(time.Second*10)
}
waitgroup少了一个done容易出现deadlock
无缓冲的channel也容易出现deadlock
适用场景
无缓冲channel适用于通知B要第一时间知道A是否已经完成
有缓冲channel适用于生产者和消费者之间的通讯
go中channel的应用场景
- 消息传递,消息过滤
- 信号广播
- 事件订阅和广播
- 任务分发
- 结果汇总
- 并发控制
- 同步和异步
5.单项channel的使用
默认情况下,channel是双向的,但是我们经常一个channel作为参数进行传递,希望对象也是单向使用
package main
import "fmt"
func main() {
//var ch1 chan int //双向的channel
//var ch2 chan<- float64 //单项channel,只能写入float64的数据
//var ch3 <-chan int //只能读取int类型的数据
/*
定义一个channel然后把它编程单向的,但是不能把单项的channel转成双向的channel
*/
c := make(chan int, 3)
var send chan<- int = c
var read <-chan int = c
send <- 1
num := <- read
fmt.Println(num)
}
模拟单项channel存取数据
package main
import (
"fmt"
"time"
)
func producer(out chan<- int) {
for i:=0;i<10 ;i++ {
out <- i*i
}
close(out)
}
func consumer(in <-chan int) {
for num := range in {
fmt.Println(num)
}
}
func main() {
/*
内部会完成自动的类型转换
*/
c := make(chan int)
go producer(c)
go consumer(c)
time.Sleep(10*time.Second)
}
6交替打印
这是一道经典题目,在Java中也有提到,交替打印这个序列
12ab34cd56ef78gh910ij1112kl1314mn1516op1718qr1920st2122uv2324wx2526yz2728
利用channel阻塞的特性来实现
package main
import (
"fmt"
"time"
)
var number, letter = make(chan bool), make(chan bool)
func printNum() {
i := 1
for {
//等待另外一个goroutine进行通知
<-number //从number进行取值的操作,如果没有值就阻塞
fmt.Printf("%d%d", i, i+1)
i += 2
letter <- true
}
}
func printLetter() {
str := "abcdefghijklmnopqrstuvwxyz"
i := 0
for {
<-letter
if i>= len(str){
return
}
fmt.Print(str[i : i+2])
i += 2
number <- true // 存入true到channel中
}
}
func main() {
go printNum()
go printLetter()
number <- true
time.Sleep(10*time.Second)
}