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,需要使用类型断言
}