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)
}
}