Go-信道Channel

信道可以想像成 Go 协程之间通信的管道。如同管道中的水会从一端流到另一端,通过使用信道,数据也可以从一端发送,在另一端接收

信道的声明

所有信道都关联了一个类型。信道只能运输这种类型的数据,而运输其他类型的数据都是非法的。

chan T 表示 T 类型的信道。

信道的零值为 nil。信道的零值没有什么用,应该像对 map 和切片所做的那样,用 make 来定义信道。

int 类型信道

var a chan int
a := make(chan int)

var a chan Person   // Person 结构体

package main

import "fmt"

func main() {  
    var a chan int
    if a == nil {
        fmt.Println("channel a is nil, going to define it")
        a = make(chan int)
        fmt.Printf("Type of a is %T", a)
    }
}

通过信道进行发送和接收

操作符:<- 
data := <- a // 读取信道 a  
a <- data // 写入信道 a

发送与接收默认是阻塞的

当把数据发送到信道时,程序控制会在发送数据的语句处发生阻塞,直到有其它 Go 协程从信道读取到数据,才会解除阻塞。与此类似,当读取信道的数据时,如果没有其它的协程把数据写入到这个信道,那么读取过程就会一直阻塞着。

信道的这种特性能够帮助 Go 协程之间进行高效的通信,不需要用到其他编程语言常见的显式锁或条件变量。

func hello(done chan bool) {
	fmt.Println("Hello world goroutine")
	done <- true  // 写入
}
func main() {
	done := make(chan bool)
	go hello(done)
	a := <- done  // 读取   <-done   没有使用数据或者把数据存储到变量中。这完全是合法的。
	fmt.Println("信道数据:",a)
	fmt.Println("main function")
}

结果:

Hello world goroutine
信道数据: true
main function

Go 主协程发生了阻塞,等待信道 done 发送的数据。该信道作为参数传递给了协程 hellohello 打印出 Hello world goroutine,接下来向 done 写入数据。当完成写入时,Go 主协程会通过信道 done 接收数据,于是它解除阻塞状态,打印出文本 main function

实例:计算一个数中每一位的平方和与立方和,然后把平方和与立方和相加并打印出来


func hello(done chan bool) {
	fmt.Println("hello go routine is going to sleep")
	time.Sleep(4 * time.Second)
	fmt.Println("hello go routine awake and going to write to done")
	done <- true
}

func calcSquares(square chan int, number int) {
	sum := 0
	for number != 0 {
		digit := number % 10
		sum += digit * digit
		number = number / 10
	}
	square <- sum
}

func calcCubes(square chan int, number int) {
	sum := 0
	for number != 0 {
		digit := number % 10
		sum += digit * digit * digit
		number = number / 10
	}
	square <- sum
}

func main() {
	done := make(chan bool)
	fmt.Println("Main going to call hello go goroutine")
	go hello(done)
	<-done

	squares := make(chan int)
	go calcSquares(squares, 589)
	cubes := make(chan int)
	go calcCubes(cubes, 589)
	squSum := <-squares
	cubSum := <-cubes
	// 已经从信道中读取了数据,就不能再次读取了
	//squSum, cubSum = <-squares, <-cubes
	fmt.Println("squSum:", squSum)
	fmt.Println("cubSum:", cubSum)
	fmt.Println("sum:", cubSum + squSum)
	fmt.Println("Main received data")
}

死锁
使用信道需要考虑的一个重点是死锁。当 Go 协程给一个信道发送数据时,照理说会有其他 Go 协程来接收数据。如果没有的话,程序就会在运行时触发 panic,形成死锁

同理,当有 Go 协程等着从一个信道接收数据时,我们期望其他的 Go 协程会向该信道写入数据,要不然程序就会触发 panic。

package main

func main() {  
    ch := make(chan int)
    ch <- 5
}

我们创建了一个信道 ch,接着在下一行 ch <- 5,我们把 5 发送到这个信道。对于本程序,没有其他的协程从 ch 接收数据。于是程序触发 panic

单向信道
只能发送或者接收数据

package main

import "fmt"

func sendData(sendch chan<- int) {   // 转成单向信道,协程中不能读取数据
    sendch <- 10
}

func main() {  
    sendch := make(chan<- int) // 只能写入数据
    //sendch := make(chan int)   // 定义一个双向信道
    go sendData(sendch)
    fmt.Println(<-sendch)  // 接受数据
}

编译器报错:
main.go:11: invalid operation: <-sendch (receive from send-only type chan<- int)

一切都很顺利,只不过一个不能读取数据的唯送信道究竟有什么意义呢?

这就需要用到信道转换(Channel Conversion)了。把一个双向信道转换成唯送信道或者唯收(Receive Only)信道都是行得通的,但是反过来就不行。

package main

import "fmt"

func sendData(sendch chan<- int) {  
    sendch <- 10
}

func main() {  
    cha1 := make(chan int)
    go sendData(cha1)
    fmt.Println(<-cha1)
}
结果:
10

关闭信道和使用 for range 遍历信道

数据发送方可以关闭信道,通知接收方这个信道不再有数据发送过来。

当从信道接收数据时,接收方可以多用一个变量来检查信道是否已经关闭。

格式:

v, ok := <- ch 

for循环,for rang循环


func forChan(ch chan int) {
	for i := 0; i < 10; i++ {
		fmt.Println("write ", i)  // 放到下面,看下打印结果
		ch <- i
		//fmt.Println("write ", i)  
	}
	fmt.Println("write end")
	close(ch)
}

func main() {
	// 信道通信
	//simpleChan()
	// 信道关闭和遍历
	ch := make(chan int)
	go forChan(ch)
	/*for {
		v, ok := <-ch
		if ok == false {
			fmt.Println("chan close ", v, ok)
			break
		}
		fmt.Println("Received ", v, ok)
	}*/

	for v := range ch {
		fmt.Println("Received ", v)
	}
}

结果:
write  0
write  1
Received  0
Received  1
write  2
write  3
Received  2
Received  3
write  4
write  5
Received  4
Received  5
write  6
write  7
Received  6
Received  7
write  8
write  9
Received  8
Received  9
write end

计算一个数的平方和,立方和的总和优化

digits方法抽取

package main

import (  
    "fmt"
)

func digits(number int, dchnl chan int) {  
    for number != 0 {
        digit := number % 10
        dchnl <- digit
        number /= 10
    }
    close(dchnl)
}
func calcSquares(number int, squareop chan int) {  
    sum := 0
    dch := make(chan int)
    go digits(number, dch)
    for digit := range dch {
        sum += digit * digit
    }
    squareop <- sum
}

func calcCubes(number int, cubeop chan int) {  
    sum := 0
    dch := make(chan int)
    go digits(number, dch)
    for digit := range dch {
        sum += digit * digit * digit
    }
    cubeop <- sum
}

func main() {  
    number := 589
    sqrch := make(chan int)
    cubech := make(chan int)
    go calcSquares(number, sqrch)
    go calcCubes(number, cubech)
    squares, cubes := <-sqrch, <-cubech
    fmt.Println("Final output", squares+cubes)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值