(1)只读只写管道
如果有个管道只允许其读,那么就声明为读。那么只写,声明为只写。
package main
import "fmt"
func main() {
//管道可以声明为只读或者只写
//默认情况下,管道是双向,可读可写 var chan1 chan int
//声明为只写管道
var chan1 chan<- int
chan1 = make(chan int, 3)
fmt.Println(chan1)
//声明为只读
var chan2 <-chan int
chan2 = make(chan int, 3)
fmt.Println(chan2)
}
这里的管道类型仍然是chan int,只读只写并不代表是类型,而是代表一种属性。
package main
import "fmt"
func main() {
var ch chan int
var exitChan chan struct{}
ch = make(chan int, 10)
exitChan = make(chan struct{}, 2)
go sender(ch, exitChan)
go receive(ch, exitChan)
fmt.Println(<-exitChan)
fmt.Println(<-exitChan)
}
func sender(ch chan<- int, exitChan chan struct{}) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
exitChan <- struct{}{}
}
func receive(ch <-chan int, exitChan chan struct{}) {
for {
if v, ok := <-ch; ok {
fmt.Println(v)
} else {
break
}
}
exitChan <- struct{}{}
}
这里主要将双向的管道ch = make(chan int, 10)传给了ch chan<- int这个形参之后,相当于将只读的操作不让你使用,但是类型依然是chan int。
(2)使用select解决从管道取数据的阻塞问题
要从管道当中读取数据需要close这个管道,不去close这个管道去遍历的时候,管道会阻塞,如果阻塞在main协程里面会发生死锁。
ch1 := make(chan int, 4) for v := range ch1 { fmt.Println(v) }这里不去使用close去关闭管道
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
C:/Users/W10/GolandProjects/goroutine/阻塞.go:18 +0x139
有些时候不好确定管道是否可以关闭,关闭的时机也不确定。
package main
import (
"fmt"
"time"
)
func main() {
//使用select可以解决从管道当中读取数据阻塞的问题
//1.定义一个管道 10个数据int
initChan := make(chan int, 10)
for i := 0; i < 5; i++ {
initChan <- i
}
//2.定义一个管道 5个数据string
stringChan := make(chan string, 5)
for i := 0; i < 5; i++ {
stringChan <- "hello" + fmt.Sprintf("%d", i)
}
//传统的方法在遍历管道的时候如果不关闭会阻塞导致死锁
//在实际开发过程中,可能无法确定什么时候关闭该管道
//可以使用select的方式可以解决
for {
select {
//如果管道一直没有关闭,也不会一直阻塞在这里导致死锁
// 它会自动的到下一个case匹配,不依赖关闭
case v := <-initChan:
time.Sleep(time.Second * 1)
fmt.Printf("从intchan读取了数据%d\n", v)
case v := <-stringChan:
time.Sleep(time.Second * 1)
fmt.Printf("从stringChan读取了数据%s\n", v)
default:
time.Sleep(time.Second * 1)
fmt.Println("都取不到了,不玩了,这里可以加入业务逻辑")
}
}
}
从intchan读取了数据0
从intchan读取了数据1
从intchan读取了数据2
从intchan读取了数据3
从intchan读取了数据4
从stringChan读取了数据hello0
从stringChan读取了数据hello1
从stringChan读取了数据hello2
从stringChan读取了数据hello3
从stringChan读取了数据hello4
都取不到了,不玩了,这里可以加入业务逻辑
都取不到了,不玩了,这里可以加入业务逻辑
都取不到了,不玩了,这里可以加入业务逻辑
都取不到了,不玩了,这里可以加入业务逻辑
如果是在函数里面,直接使用return,那么就直接退出协程的函数。
(3)goroutine中使用recover,解决协程中出现panic,导致程序崩溃问题
说明:如果我们起了一个协程,但是这个协程出现了panic,如果我们没有捕获这个panic,就会造成整个程序崩溃,这时我们可以在goroutine中使用recover来捕获panic进行处理。这样即使这个协程发生的问题,但是主线程仍然不受影响,可以继续执行。
package main
import (
"fmt"
"time"
)
// 函数
func sayHello() {
for i := 0; i < 10; i++ {
time.Sleep(time.Second * 1)
fmt.Println("func sayHello")
}
}
// Test1 函数
func Test1() {
//定义一个map
var myMap map[int]string
//这种写法是错误的,因为map是需要先make一下的
myMap[0] = "golang" //这块协程报错导致整个程序崩溃
}
func main() {
go sayHello()
go Test1()
time.Sleep(time.Second * 5)
fmt.Println("func main")
}
panic: assignment to entry in nil map
goroutine 7 [running]:
main.Test1()
C:/Users/W10/GolandProjects/goroutine/recover.go:21 +0x25
created by main.main
C:/Users/W10/GolandProjects/goroutine/recover.go:26 +0x31
进程 已完成,退出代码为 2
显然影响了主线程的执行。这里可以使用错误处理机制recover+defer来解决。有些时候,一个协程崩溃了,我们并不希望它影响到其他的协程和主线程的执行。
修改过后的代码:
package main
import (
"fmt"
"time"
)
// 函数
func sayHello() {
for i := 0; i < 10; i++ {
time.Sleep(time.Second * 1)
fmt.Println("func sayHello")
}
}
// Test1 函数
func Test1() {
//这里可以使用错误处理机制defer + recover
defer func() {
//捕获test抛出的panic
if err := recover(); err != nil {
fmt.Println("Test1()发生异常", err)
}
}()
//定义一个map
var myMap map[int]string
//这种写法是错误的,因为map是需要先make一下的
myMap[0] = "golang" //这块协程报错导致整个程序崩溃
}
func main() {
go sayHello()
go Test1()
time.Sleep(time.Second * 5)
fmt.Println("func main")
}
Test1()发生异常 assignment to entry in nil map
func sayHello
func sayHello
func sayHello
func sayHello
func main