并发
goroutine和wg
func main() {
// 来实现并发任务的同步执行
// wg 防止main的goroutine执行完了之后,自己定义的goroutine还没有执行完
var wg sync.WaitGroup
for i:=0; i<5; i++{
go func(index int) {
defer wg.Done() // defer会在最后执行 wg.Done 计数器减1
fmt.Println(index)
}(i)
wg.Add(1) // 计数器加1 这个数字是可以随便定义的,但是在Done里自己处理
}
wg.Wait() // 阻塞,直到计数器变为0
fmt.Println("meila")
}
channel
ch := make(chan int 10) //定义一个i能存储10个int类型的管道
ch <- 1 // 把1发送到管道中
a := <- ch // 从管道ch中拿值
close(ch) // 关闭管道
- for range可以从管道中取值,通道关闭后会退出for range
- 通道被关闭后,再从通道中取值会一直收到零值
- 无缓冲通道必须有一个接收者,也被叫做阻塞的通道
- 无缓冲和有缓冲的区别就是有没有在设置容量
package main
import (
"fmt"
)
func f3(ch chan int) {
for v := range ch {
fmt.Println(v)
}
} // for range来接受通道中的值,就不用和f1自己来判断了
func f1(ch1 chan int) {
for i := 0; i < 100; i++ {
ch1 <- i
}
close(ch1)
}
func f2(ch1, ch2 chan int) {
for {
i, ok := <-ch1 // 多返回值模式,没有值了之后ok会是false
if !ok {
break
}
ch2 <- i * i
}
close(ch2)
}
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go f1(ch1)
go f2(ch1, ch2)
/*
执行f1和f2要加上goroutine,因为ch1和ch2都是无缓冲的通道,无缓冲的通道必须有一个接收方才能发送成功
不加goroutine的话,就会造成一直在等待接收者,造成死锁
无缓冲就是类似快递员送快递,必须有人签收
有缓冲就是类似快递放到快递柜里,容量就是这个快递柜最多能放多少快递
*/
for i := range ch2 { // for range可以用来判断通道关闭
fmt.Println(i)
}
for i := 0; i < 5; i++ {
go func() {
fmt.Println(i)
}()
}
}
// <- chan int 只接受通道 x := <- ch 从通道中接受值
// chan <- 只发送通道 ch <- 10 把10发送到通道中
- select 多路复用可处理一个或多个 channel 的发送/接收操作。如果多个 case 同时满足,select 会随机选择一个执行。对于没有 case 的 select 会一直阻塞,可用于阻塞 main 函数,防止退出。实际开发中可能存在不知道什么时候退出的情况,就可以使用select
package main
import "fmt"
func main() {
ch := make(chan int, 1)
for i := 1; i <= 10; i++ {
select {
case x := <-ch:
fmt.Println(x)
case ch <- i:
}
}
}
/* 输出
1
3
5
7
9
*/
并发和锁
- 为了防止多个goroutine操作同一个资源,需要在操作的时候加上锁
- 锁有互斥锁和读写互斥锁
package main
import (
"fmt"
"sync"
"time"
)
var (
x int64
wg sync.WaitGroup
mutex sync.Mutex // 互斥锁,同一时间只有一个人能用
rwMutex sync.RWMutex // 读写互斥锁,用于读多写少的场景
/*
读写锁分为两种:读锁和写锁。
当一个 goroutine 获取到读锁之后,其他的 goroutine 如果是获取读锁会继续获得锁,如果是获取写锁就会等待;
当一个 goroutine 获取写锁之后,其他的 goroutine 无论是获取读锁还是写锁都会等待
*/)
func writeWithLock() {
mutex.Lock() // 加锁
x = x + 1
time.Sleep(10 * time.Millisecond)
mutex.Unlock() // 解锁
wg.Done()
}
func readWithLock() {
mutex.Lock()
time.Sleep(time.Millisecond)
mutex.Unlock()
wg.Done()
}
func writeWithRWLock() {
rwMutex.Lock() // 加写锁
x = x + 1
time.Sleep(10 * time.Millisecond)
rwMutex.Unlock() //解开写锁
wg.Done()
}
func readWithRWLock() {
rwMutex.RLock() // 加读锁
time.Sleep(time.Millisecond)
rwMutex.RUnlock() // 解开读锁
wg.Done()
}
func do(wf, rf func(), wc, rc int) {
start := time.Now()
// wc个并发写操作
for i := 0; i < wc; i++ {
wg.Add(1)
go wf()
}
// rc个并发读操作
for i := 0; i < rc; i++ {
wg.Add(1)
go rf()
}
wg.Wait()
cost := time.Since(start)
fmt.Printf("x:%v cost:%v\n", x, cost)
}
func main() {
do(writeWithLock, readWithLock, 10, 1000)
do(writeWithRWLock, readWithRWLock, 10, 1000) // x:10 cost:117.207592ms
}
sync方法
- sync.WaitGroup 用来实现并发任务的同步Add(x) 计数器加xDone() 计数器减1Wait() 阻塞,直到计数器变为0
- sync.Once 确保某些操作只会执行一次,比如加载配置文件之类的
package main
import (
"image"
"sync"
)
var icons map[string]image.Image
var loadIconsOnce sync.Once
func loadIcons() {
icons = map[string]image.Image{
/*
不用Sync.Once的话,在并发操作时,可能刚初始化完map就会被调用,倒是值一直时nil
*/ "left": loadIcon("left.png"),
"up": loadIcon("up.png"),
"right": loadIcon("right.png"),
"down": loadIcon("down.png"),
}
}
// Icon 是并发安全的
func Icon(name string) image.Image {
loadIconsOnce.Do(loadIcons)
/*
sync.Once只有一个Do方法
确保了loadIcon只会被执行一次
*/ return icons[name]
}
package main
import (
"fmt"
"strconv"
"sync"
)
// 并发安全的map
var m = sync.Map{}
func main() {
wg := sync.WaitGroup{}
// 对m执行20个并发的读写操作
for i := 0; i < 20; i++ {
wg.Add(1)
go func(n int) {
key := strconv.Itoa(n)
m.Store(key, n) // 存储key-value
value, _ := m.Load(key) // 根据key取值
fmt.Printf("k=:%v,v:=%v\n", key, value)
wg.Done()
}(i)
}
wg.Wait()
}