16.golang之channel (管道)

1. channel(管道)——看个需求

1)需求:现在需要计算 1-200 的各个数的阶乘,并且把各个数的阶乘放到map中。最后显示出来。

要求使用goroutine完成。

2)分析思路

使用goroutine来完成,效率高,但是会出现并发/并行安全问题

这里就提出了不同goroutine如何通信的问题

代码实现:

1)使用goroutine来完成(看看使用goroutine并发完成会出现什么问题?然后我们去解决)

2)在运行某个程序时,如何知道是否存在资源竞争问题。方法很简单,在编译该程序时,增加一个参数  -race即可【示意图】

package main
import (
	"fmt"
	"time"
)
// 需求:现在要计算1-200的各个数的阶乘,并且把各个数的阶乘放入到map中。最后显示出来。要求使用goroutine完成。
// 思路:
// 1. 编写一个函数,来计算各个数的阶乘,并放入到map中。
// 2. 我们启动的协程多个,统计的结果放入到map中
// 3. map应该是全局的
var (
	myMap = make(map[int]int,10)
)
func factorial(n int) {
	res := 1
	for i := 1; i <= n; i++ {
		res *= i
	}
	// 这里我们将 res 放入到myMap中
	myMap[n] = res // concurrent map writes
}

func main() {
	for i := 1; i <= 200; i++ {
		go factorial(i)
	}
	// 休眠十秒钟
	time.Sleep(time.Second*10)
	
	// 输出结果
	for k, v := range myMap {
		fmt.Printf("myMap[%d]=%d\n",k,v)
	}
}

4)示意图

 2. 不同goroutine之间如何通讯

1)全局变量的互斥锁

2)使用管道channel来解决

3. 使用全局变量加锁同步改进程序

因为没有对全局变量m加锁,因此会出现资源争夺问题,代码会出现错误,提示 concurrent map writes

解决方案:加入互斥锁

我们的数的阶乘很大,结果会越界,可以将求阶乘改成 sum += uint64(i)

代码改进:

package main
import (
	"fmt"
	"time"
	"sync"
)
// 需求:现在要计算1-200的各个数的阶乘,并且把各个数的阶乘放入到map中。最后显示出来。要求使用goroutine完成。
// 思路:
// 1. 编写一个函数,来计算各个数的阶乘,并放入到map中。
// 2. 我们启动的协程多个,统计的结果放入到map中
// 3. map应该是全局的
var (
	myMap = make(map[int]int,10)
	// 声明一个全局的互斥锁
	// lock是一个全局的互斥锁
	// sync是包:synchornized同步
	// Mutex:互斥
	lock sync.Mutex
)
func factorial(n int) {
	res := 1
	for i := 1; i <= n; i++ {
		res *= i
	}
	// 这里我们将 res 放入到myMap中
	// 加锁
	lock.Lock()
	myMap[n] = res // concurrent map writes
	// 解锁
	lock.Unlock()
}

func main() {
	for i := 1; i <= 20; i++ {
		go factorial(i)
	}
	
	// 休眠十秒钟
	time.Sleep(time.Second*10)
	lock.Lock()
	// 输出结果
	for k, v := range myMap {
		fmt.Printf("myMap[%d]=%d\n",k,v)
	}
	lock.Unlock()
}

4. 为什么需要channel

1)前面使用全局变量加锁同步来解决goroutine的通讯,但不完美

2)主线程在等待所有goroutine全部完成的时间很难确定,我们这里设置10秒,仅仅是估算。

3)如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有goroutine处于工作状态,这时也会随主线程的退出而销毁

4)通过全局变量加锁同步来实现通讯,也并不利用多个协程对全局变量的读写操作

5)上面种种分析都在呼唤一个新的通讯机制 -> channel

5. channel的基本介绍

1)channle本质就是一个数据结构-队列【示意图】

2)数据是先进先出【FIFO:first in first out】 

3)线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的

4)channel有类型的,一个string的channel只能存放string类型数据

6. 定义/声明channel

var 变量名  chan 数据类型

举例:

var intChan  chan  int(intChan用于存放int数据)

var mapChan chan map[int]string (mapChan用于存放map[int]string类型)

var perChan  chan Person

var perChan2 chan *Person

...

说明:

channel  是引用类型

channel  必须初始化才能写入数据,即make后才能使用

管道是有类型的,intChan  只能写入  整数 int

7.  管道的初始化,写入数据到管道,从管道读取数据及基本的注意事项

package main
import (
	"fmt"
)

func main() {
	// 演示一下管道的使用
	// 1. 创建一个可以存放3个int类型的管道
	var intChan chan int
	intChan = make(chan int, 3)

	// 2. 看看intChan是什么
	fmt.Println("intChan的值=%v intChan本身的地址=%p\n",intChan,&intChan)

	// 3. 向管道写入数据
	intChan <- 10
	num := 211
	intChan <- num
	intChan <- 50
	// 注意:当我们给管道写入数据时,不能超过其容量
	// intChan <- 98

	// 4. 看看管道的长度和cap(容量)
	fmt.Printf("channel len=%v  cap=%v\n",len(intChan),cap(intChan)) // 3,3
	
	// 5. 从管道中读取数据
	var num2 int
	num2 = <-intChan
	fmt.Println("num2=",num2)
	fmt.Printf("channel len=%v cap=%v \n",len(intChan),cap(intChan))

	// 6. 在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报 deadlock错误
	num3 := <-intChan
	num4 := <-intChan
	// num5 := <-intChan
	fmt.Println("num3=",num3,"num4=",num4)
}

8. channel使用的注意事项

1)channel中只能存放指定的数据类型

2)channel的数据放满后,就不能再放入了

3)如果从channel取出数据后,可以继续放入

4)在没有使用协程的情况下,如果channel数据取完了,再取就会报dead lock错误

9. 读写 channel案例演示

1)创建一个intChan,最多可以存放3个,演示存3数据到intChan,然后再取出这三个int

package main
import (
	"fmt"
)

func main() {
	var intChan chan int
	intChan = make(chan int,3)
	intChan <- 10
	intChan <- 20
	intChan <- 10
	// 因为 intChan的容量为3,再存放会报告dead lock
	num1 := <- intChan
	num2 := <- intChan
	num3 := <- intChan
	// 因为 intChan 这时已经没有数据了,再取就会报告 dead lock
	fmt.Printf("num1=%v num2=%v num3=%v",num1,num2,num3)
}

2)创建一个mapChan,最多可以存放10个map[string]string的key-val,演示写入和读取

package main
import (
	"fmt"
)

func main() {
	var mapChan chan map[string]string
	mapChan = make(chan map[string]string, 10)
	m1 := make(map[string]string,20)
	m1["city1"] = "南昌"
	m1["city2"] = "吉安"

	m2 := make(map[string]string)
	m2["hero1"] = "张飞"
	m2["hero2"] = "关羽"

	mapChan <- m1
	mapChan <- m2

	map1 := <-mapChan
	map2 := <-mapChan
	fmt.Printf("map1=%v map2=%v",map1,map2)
}

3)创建一个catChan,最多可以存放10个Cat结构体变量,演示写入和读取的用法

package main
import (
	"fmt"
)

type Cat struct {
	Name string
	Age int
}

func main() {
	var catChan chan Cat
	catChan = make(chan Cat, 3)

	c1 := Cat{"tom",3}
	c2 := Cat{"jarry",5}
	catChan <- c1
	catChan <- c2
	// 取出
	cat1 := <- catChan
	cat2 := <- catChan
	fmt.Printf("cat1=%v cat2=%v",cat1,cat2)
}

4)创建一个catChan2,最多可以存放10个*Cat变量,演示写入和读取的用法

package main
import (
	"fmt"
)

type Cat struct {
	Name string
	Age int
}

func main() {
	var catChan chan *Cat
	catChan = make(chan *Cat, 10)

	c1 := Cat{"tom", 3}
	c2 := Cat{"jary", 5}
	catChan <- &c1
	catChan <- &c2
	// 取出
	cat1 := <- catChan
	cat2 := <- catChan
	fmt.Printf("cat1=%v cat2=%v",cat1,cat2)
}

5)创建一个allChan,最多可以存放10个任意数据类型变量,演示和写入和读取的用法

package main
import (
	"fmt"
)

type Cat struct {
	Name string
	Age int
}

func main() {
	var allChan chan interface{}
	allChan = make(chan interface{}, 10)
	c1 := Cat{"tom", 10}
	c2 := Cat{"jarry", 20}
	allChan <- c1
	allChan <- c2
	allChan <- 10
	allChan <- "jack"

	// 取出
	cat1 := <- allChan
	cat2 := <- allChan
	v1 := <- allChan
	v2 := <- allChan
	fmt.Printf("cat1=%v cat2=%v v1=%v v2=%v",cat1,cat2,v1,v2)
}

6)看下面的代码,会输出什么?

package main
import (
	"fmt"
)

type Cat struct {
	Name string
	Age int
}

func main() {
	var allChan chan interface{}
	allChan = make(chan interface{}, 10)
	c1 := Cat{"tom", 10}
	c2 := Cat{"jarry", 20}
	allChan <- c1
	allChan <- c2
	allChan <- 10
	allChan <- "jack"

	// 取出
	cat1 := <- allChan
	fmt.Printf("cat1=%T  cat1=%v\n",cat1,cat1)
	newCat := cat1.(Cat)
	fmt.Println(newCat.Name)
	// fmt.Println(cat1.Name) // 报错,因为取出来的是一个 interface,需要使用类型断言
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值