【go】【并发】channel管道

1.用共享变量加锁解决同步问题 毛病太大
主线程的休眠时间不好确定
不利于多个协程的读写操作,因此需要channel

2.channel本质是一个数据结构-duilie
数据先进先出
线程安全,多goroutine访问时,不需要加锁
channel有类型。一个string的channel只能放string数据

3.channel是引用类型
必须先make才能用
channel读写基础

	var intChan chan int
	intChan = make(chan int, 3)
	fmt.Println("intChan =", intChan)
	fmt.Printf("intChan本身的地址%p\n", &intChan)

	//写入数据
	intChan<- 10
	num := 233
	intChan<- num

	//管道的长度和容量【不能自动增长】
	//给管道写入数据时 一定不能超过cap
	fmt.Printf("channel len = %v\ncap = %v\n", 
		len(intChan), cap(intChan))

	//取出数据
	var n2 int
	n2 = <-intChan
	fmt.Println("n2 =", n2)
	fmt.Printf("channel len = %v\ncap = %v\n", 
		len(intChan), cap(intChan))
	//取完了再取 就会报deadlock

4.对于空接口chan
取出元素还要经过类型断言

val := <-allChan
newVal := val.(myType)

5.可以关闭chan 一旦关闭就不能写,但是可以读

	intChan := make(chan int, 100)
	intChan<- 100
	intChan<- 22
	close(intChan)
	//不能再写入数据到channel
	n1 := <-intChan
	fmt.Println(n1)

6.支持用for-range遍历
如果遍历时不先关闭 会死锁

先关闭,才能正常遍历

	intChan := make(chan int, 100)
	for i := 0; i < 100; i++{
		intChan<- i * 2 //放入100个数据
	}
	//遍历 没有所谓的下标 必须按顺序取
	close(intChan)
	for v := range intChan {
		fmt.Println("v =", v)
	}

7.管道简单使用
如何让主线程知道读协程完成任务了
设置一个exit管道,完成任务后放入指示遍历
主线程中循环等待,直到能拿出来值,说明协程工作完成。

//写协程向chan中写入50个整数
//读协程读取数据 主线程要等两个协程都完成工作才能退出
func writeData(intChan chan int){
	for i := 0; i < 50; i++ {
		intChan<- i
	}
	close(intChan) // 关闭不影响读 可以循环读
}

//read
func readData(intChan chan int, exitChan chan bool){
	for {
		v, ok := <-intChan
		if ok {
			fmt.Println(v)
		}else{
			break //如果不close 读就会死锁
		}
	}
	//任务完成 告知exitChan
	exitChan<- true
	close(exitChan)
}

func main(){
	//管道在主线程创建
	intChan := make(chan int, 50)
	exitChan := make(chan bool, 1)
	go writeData(intChan)
	go readData(intChan, exitChan)
	for {
		v := <-exitChan
		if v {
			break
		}
	}
}

即是读协程很慢,由于异步机制的存在,整个程序也能完美运行。
不会发生死锁。写协程能够察觉到有人在取出数据,并耐心地等待,直到任务完成。

//写协程向chan中写入50个整数
//读协程读取数据 主线程要等两个协程都完成工作才能退出
func writeData(intChan chan int){
	for i := 0; i < 50; i++ {
		intChan<- i
		fmt.Println("wirtten :", i)
	}
	close(intChan) // 关闭不影响读 可以循环读
}

//read
func readData(intChan chan int, exitChan chan bool){
	for {
		v, ok := <-intChan
		if ok {
			fmt.Println(v)
		}else{
			break //如果不close 读就会死锁
		}
		time.Sleep(time.Millisecond)
	}
	//任务完成 告知exitChan
	exitChan<- true
	close(exitChan)
}

func main(){
	//管道在主线程创建
	intChan := make(chan int, 40)
	exitChan := make(chan bool, 1)
	go writeData(intChan)
	go readData(intChan, exitChan)
	for {
		v := <-exitChan
		if v {
			break
		}
	}
}

9.统计1-8000的数字中哪些是素数
协程与管道协作流程图:

func putNum(intChan chan int) {
	for i := 2; i <= 8000; i++ {
		intChan<- i
	}
	close(intChan)
}

func primeNum(intChan chan int, primeChan chan int,
	exitChan chan bool) {
	var flag bool
	for {
		num, ok := <-intChan
		if !ok {
			break
		}
		flag = true // 先假定是素数
		//判断素数
		for i := 2; i <= num / 2; i++ {
			if num % i == 0 {
				flag = false
				break
			}
		}
		if flag {
			primeChan<- num
		}
	}
	fmt.Println("有一个协程取不到数据 结束任务")
	//不能关闭primeChan
	exitChan<- true
}

func main(){
	intChan := make(chan int, 1000)
	primeChan := make(chan int, 1000) //放结果
	//标识退出Channel
	exitChan := make(chan bool, 4)

	//开启协程 向intChan放入1-8000
	go putNum(intChan)
	//开启4个协程 从intChan取数据并判断
	//如果是 放入primeChan
	for i := 0; i < 4; i++ {
		go primeNum(intChan, primeChan, exitChan)
	}

	go func() {
		for i := 0; i < 4; i++ {
			<-exitChan // 从中取4个结果
		}
		close(primeChan)
	}() // 表示调用 空参数

	for {
		res, ok := <-primeChan // close后才能取到!ok
		if !ok {
			break
		}
		fmt.Println(res)
	}
	fmt.Println("main线程退出")
}

精妙的配合。
结果的一部分
注意到在计算协程结束前,就已经有结果输出了。在主线程开辟分支后,继续工作,其可以正常的从结果管道中取出结果。
也不用担心结果管道大小不够,因为主线程是在不断向外取的。

注意最后的if判断中ok取到false,而res取到0,已经没有值了

查看程序用时
不要写在主线程,不然只能看到0

	go func() {
		for i := 0; i < 4; i++ {
			<-exitChan // 从中取4个结果
		}
		close(primeChan)
		end := time.Now().Unix()
		fmt.Println("程序耗时: ", end - start)
	}() // 表示调用 空参数

10.channel可以声明为只读或只写
可以写在函数头,防止函数内误操作

	//默认是双向的
	//声明为只写
	var chan1 chan<- int
	//make不要带箭头 type还是chan int
	chan1 = make(chan int, 3)

	//声明为只读
	var chan2 <-chan int

11.select解决从管道取数据的阻塞问题

以前取数据前要close,否则会阻塞
要希望不要close也能取,可以用select

	chan1 := make(chan int, 10)
	chan2 := make(chan string, 5)
	for i := 0; i < 10; i++ {
		chan1<- i
	}
	for i := 0; i < 5; i++ {
		chan2<- "hello" + fmt.Sprintf("%d", i)
	}
	label: // 最好不要用label
	for {
		select {
			//如果管道没有关闭 也不会一直阻塞而死锁
			//会自动到下一个case匹配
		case v := <-chan1:
			fmt.Println("从chan1读取了数据:", v)
		case v := <-chan2:
			fmt.Println("从chan2读取了数据:", v)
		default:
			fmt.Println("都取不到了^^")
			break label
		}
	}

12.goroutine中用recover,结局协程中出现panic,导致程序崩溃的 问题

func sayHello(){
	for i := 0; i < 10; i++ {
		time.Sleep(time.Second)
		fmt.Println("Hello World!")
	}
}

func test(){
	//用defer+recover解决
	defer func() {
		//捕获test抛出的panic
		if err := recover(); err != nil {
			fmt.Println("test() error:", err)
		}
	}()
	var mp map[int]string
	mp[0] = "golang" // 错误的用法 没有make
}

func main(){
	go sayHello()
	go test()
	for i := 0; i < 10; i++ {
		time.Sleep(time.Second)
		fmt.Println("main() print:", i)
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值