「Golang成长之路」并发之Channel下
一、channel(通道)的介绍
如果说goroutine是GO并发的执行体,channel(通道)就是他们的连接。channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。
每一个通道是一个具体的类型,叫做通道的元素类型。如有一个int类型的通道就写成:chan int
当然这里也可以使用内置函数——make函数来创建一个通道:
ch := make(chan int) //ch是 'chan int ' 类型
二、 channel的语法
- chanDemo()
创建channel:
和普通的int、float等类型是一样
我们也可以使用内置函数make来创建:var ch1 chan int //此时 ch1 == nil var ch2 chan float32 //此时 ch2 == nil var ch3 chan string //此时 ch3 == nil
ch1 := make(chan int) ch2 := make(chan float32) ch3 := make(chan string)
package main import "fmt" func main(){ ch := make(chan int) //创建channel ch <- 1 //向channel里发数据 n := <- ch //从channel收数据 fmt.Println(n) }
这里会报错:all goroutine air asleep - deadlock!
原因:channel是用于goroutine和goroutine之间的通信管道,在上面的代码中我们只有一个主goroutine(main)所以没有人来接收ch中的信息,会造成死锁。
这里我们需要启动一个goroutine:
package main
import "fmt"
func chanDemo() {
ch := make(chan int)
go func(){ //参见函数式编程:https://learnku.com/articles/59902
for {
n := <-ch
fmt.Println(n)
}
}()
ch <- 100
ch <- 200
ch <- 300
time.Sleep(time.Millisecond) //为了让所有数据输出,需要规定程序运行时间
}
func mian(){
chanDemo()
}
打印结果为:
100
200
300
- channel可作为参数
在函数式编程中函数是一等公民,函数可作为参数、返回值等
channel也一样,也可以作为参数,返回值。
package main
import "fmt"
func worker(id int ch chan int ){ //将channel作为参数
for {
n := <- ch
fmt.Printf("worker %d received %d\n",id, n )
}
}
func chanDemo(){
ch := make( chan int)
go worker(0, ch) //开一个并发
ch <- 100
ch <- 200
ch <- 300
time.Sleep(time.Millisecond) //为了让所有数据输出,需要规定程序运行时间
}
func main(){
chanDemo()
}
打印结果为:
worker 0 received 100
worker 0 received 200
worker 0 received 300
这里我们可以随意创建,创建10goroutine:
将chanDemo()改一下
func chanDemo(){
var channels [10]chan int //创建channel数组
for i := 0; i < 10; i++{
channels[i] = make(chan int)
go worker(i, channels[i]) //创建10个goroutine
}
for i := 0; i < 10; i++{
channels[i] <- 'a' + 1
}
time.Sleep(time.Millisecond)
}
打印结果:
worker 4 received 98
worker 0 received 98
worker 1 received 98
worker 2 received 98
worker 9 received 98
worker 6 received 98
worker 3 received 98
worker 8 received 98
worker 5 received 98
worker 7 received 98
* 分析:在修改的chanDemo()中,我们开了10个goroutine,而每一个goroutine都分发了一个channel,从达到每一个goroutine都可以和主goroutine(main)通信。*
- channel可作返回值
同样channel也可以作为返回值
打印结果:func creatworker(id int) chan int { c := make(chan int) go func() { for { n := <-c fmt.Printf("worker %d received %d\n", id, n) } }() return c //在creatworker()中主要是go func()在真正的在做事,c创建后立即被返回 } // func chanDemo(){ var channels [10]chan int //创建channel数组 for i := 0; i < 10; i++{ channels[i] = creatworker(i) } for i := 0; i < 10; i++{ channels[i] <- 'a' + 1 } time.Sleep(time.Millisecond) } // func main() { chanDemo() }
worker 9 received 98
worker 8 received 98
worker 5 received 98
worker 6 received 98
worker 0 received 98
worker 3 received 98
worker 2 received 98
worker 1 received 98
worker 4 received 98
worker 7 received 98
3.bufferedChannel(缓冲通道)
在前面内容里:
package main
import "fmt"
func main(){
ch := make(chan int) //创建channel
ch <- 1 //向channel里发数据
n := <- ch //从channel收数据
fmt.Println(n)
}
会报错:all goroutine air asleep - deadlock!
是因为没有人去收channel的数据,但是在上面代码中我们发了1,就必须收1,这样比较好资源,所以我们使用缓冲通道,就可有避免死锁了。
我们这样定义:
ch := make(chan int, 3) //创建一个缓冲容量为3的通道
func bufferedChannel(){
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
}
func main() {
bufferedChannel()
}
这样run就不会deadlock,当然如果再向ch发数据就会deadlock。
现在仍然使用goroutine来收数据:
func worker(id int,c chan int){
for {
n := <-c
fmt.Printf("worker %d received %c\n", id, n)
}
}
func bufferedChannel(){
ch := make(chan int, 3)
go worker(0, ch)
for i := 0; i < 10; i++{
ch <- 'a' + i
}
time.Sleep(time.Millisecond)
}
可以看出,只要有人收,缓冲区满了,也不会deadlock。
打印结果:
worker 0 received a
worker 0 received b
worker 0 received c
worker 0 received d
worker 0 received e
worker 0 received f
worker 0 received g
worker 0 received h
worker 0 received i
- channelClose
channel什么时候发完了?
在前面的代码中,我们知道channel发完了,原因是:我们在main中调用的函数运行结束了,main结束了,程序也就退出了,在并发编程中,我们需要知道数据是什么时候发送结束的。
(1). close()方法
func worker(id int,c chan int){ for { n := <-c fmt.Printf("worker %d received %c\n", id, n) } } func channelClose(){ ch := make(chan int, 3) go worker(0, ch) ch <- 'a' ch <- 'b' ch <- 'c' ch <- 'd' close(ch) time.Sleep(time.Millisecond) } func main(){ channelClose() }
使用Close方法后数据收完后,就一直打印(打印time.Millisecond的时间)空串(或0)
打印结果:
worker 0 received a
worker 0 received b
worker 0 received c
worker 0 received d
worker 0 received
worker 0 received
worker 0 received
worker 0 received
worker 0 received
worker 0 received
worker 0 received
worker 0 received
worker 0 received
……
……
……
(2). n, ok := <- c
func worker(id int,c chan int){ for { n, ok := <- c if !ok{ break } fmt.Printf("worker %d received %c\n", id, n) } } func channelClose(){ ch := make(chan int) go worker(0, ch) ch <- 'a' ch <- 'b' ch <- 'c' ch <- 'd' close(ch) time.Sleep(time.Millisecond) } func main() { channelClose() }
打印结果:
worker 0 received a
worker 0 received b
worker 0 received c
worker 0 received d
(3). range
func worker(id int,c chan int){ for n := range c{ fmt.Printf("worker %d received %c\n", id, n) } } func channelClose(){ ch := make(chan int) go worker(0, ch) ch <- 'a' ch <- 'b' ch <- 'c' ch <- 'd' close(ch) time.Sleep(time.Millisecond) } func main() { channelClose() }
打印结果:
worker 0 received a
worker 0 received b
worker 0 received c
worker 0 received d
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: