Channel相关
1.go中解决共享变量访问安全问题
1.1 使用自带的允许多协程访问的数据类型
数组, 切片, map的并发读写问题
go语言中数组和切片支持并发读写,
但是如果在不加任何锁的情况下,读写后的结果是无法预期的
go语言中map是不支持并发读写的,
如果多个协程并发读写同一个map的话,会直接报错
go语言原生的map不支持并发读写的, 但是 sync.Map是支持并发读写的
package main
import (
"fmt"
"sync"
"time"
)
// sync.Map: 支持并发访问的map
var mp sync.Map
func rwGlobalMemory() {
//如果mp中有目标key,那么打印对应的value 如果没有,存入指定的value值
//Load:查找方法
//Store:存入方法
if value, exists := mp.Load("mykey"); exists {
fmt.Println(value)
} else {
mp.Store("mykey", "myvalue")
}
}
func main() {
//开启多个协程调用读写map的方法
go rwGlobalMemory()
go rwGlobalMemory()
go rwGlobalMemory()
go rwGlobalMemory()
//这里简单的休眠1秒, 等到所有的协程完成任务
time.Sleep(time.Second)
}
上面的代码, 输出结果有多种情况:
可能输出不确定个myvalue
1.2 使用管道解决
CSP即communicating sequential process, 在 go语言里就是channel.
CSP讲究的是 “以通信的方式来共享内存”
package main
import (
"bufio"
"fmt"
"io"
"os"
)
var (
content = make(chan string, 1000)
readFileCh = make(chan struct{}, 3)
writerFileCh = make(chan struct{}, 0)
)
/*
*
将三个文件的内容,全部读取到同一个channel中
*/
func main() {
for i := 0; i < 3; i++ {
readFileCh <- struct{}{}
}
go readFile("data/1.txt")
go readFile("data/2.txt")
go readFile("data/3.txt")
//全部写入同一个big.txt文件
go writeFile("data/big.txt")
<-writerFileCh
}
func readFile(infile string) {
fin, err := os.Open(infile)
if err != nil {
fmt.Println(err)
}
defer fin.Close()
reader := bufio.NewReader(fin)
for {
line, err := reader.ReadString('\n')
if err == nil {
content <- line
} else {
if err == io.EOF {
if len(line) > 0 { //输入文件的最后一行没有换行符
content <- line + "\n"
}
break
} else {
fmt.Println(err)
}
}
}
<-readFileCh
//如果readFileCh为空了, 将content管道关闭
if len(readFileCh) == 0 {
close(content)
}
}
func writeFile(mergedFile string) {
//open文件, 赋值权限创建,覆盖,读写
fout, err := os.OpenFile(mergedFile, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666)
if err != nil {
fmt.Println(err)
}
defer fout.Close()
writer := bufio.NewWriter(fout)
//for range 遍历并取走管道中的元素. 当content管道为空且被close时, for循环才会退出
for line := range content {
writer.WriteString(line)
}
writer.Flush()
writerFileCh <- struct{}{}
}
channel的死锁问题
- Channel满了, 就阻塞写; Channel空了, 就阻塞读
- 阻塞之后会交出cpu, 去执行其它协程, 希望其它协程能帮自己解除阻塞
- 如果阻塞发生在main协程里, 并且没有其它子协程可以执行,
那就可以确定 “希望永远等不来”, 自己把自己杀掉, 报一个 fatal error:deadlock出来 - 如果阻塞发生在子协程里, 就不会发生死锁, 因为至少 main线程是一个值得等待的 “希望”,
会一直等(阻塞)下去
练习1
package main
import (
"fmt"
"time"
)
/*
管道间通信
*/
func main() {
ch := make(chan struct{}, 1)
ch <- struct{}{} //有一个缓冲可以用, 无序阻塞, 可以立即执行
go func() { //子协程1
time.Sleep(5 * time.Second) //sleep一个很长的时间
<-ch //如果本行代码注释掉, main协程5秒钟后会报fatal error
fmt.Println("sub routime 1 over")
}()
//由于子协程1已经启动, 寄希望于子协程1帮助自己解除阻塞, 所以会一直等子协程1执行结束.
//如果子协程1执行结束后没帮自己解除阻塞, 则希望完全破灭, 报出deadlock
ch <- struct{}{}
fmt.Println("send to channel in main routime")
go func() { //子协程2
time.Sleep(2 * time.Second)
ch <- struct{}{} //channel已满,子协程2会一直阻塞在这一行
//这行代码是没有机会执行到的, 因为主线程已经向管道中放了一个元素, 管道已经满了, 会在上一行阻塞住
fmt.Println("sub routime 2 over")
}()
time.Sleep(3 * time.Second)
fmt.Println("main routime exit")
}
练习2
/*
管道间通信2
*/
func traveseChannel3() {
ch := make(chan int, 3)
go func() {
ch <- 1
ch <- 2
ch <- 3
//如果把这行close动作注释, 那么主线程中的遍历就会阻塞, 一直等待新元素进入管道
//但是所有协程已经执行结束了, 再没有人会来放入新元素了, 就会产生死锁报错
close(ch)
}()
for ele := range ch { //遍历并取走管道中的元素
fmt.Println(ele)
}
fmt.Println("bye bye")
}
func main() {
traveseChannel3()
}