Channel相关

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的死锁问题

  1. Channel满了, 就阻塞写; Channel空了, 就阻塞读
  2. 阻塞之后会交出cpu, 去执行其它协程, 希望其它协程能帮自己解除阻塞
  3. 如果阻塞发生在main协程里, 并且没有其它子协程可以执行,
    那就可以确定 “希望永远等不来”, 自己把自己杀掉, 报一个 fatal error:deadlock出来
  4. 如果阻塞发生在子协程里, 就不会发生死锁, 因为至少 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()
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值