go语言管道(channel)

Posted 两片空白

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go语言管道(channel)相关的知识,希望对你有一定的参考价值。

前言

        channel式go语言协程中数据通信的双向通道。但是在实际应用中,为了代码的简单和易懂,一般使用的channel是单向的。

使用

1. channel的定义和收发数据

package channel


func main()
	//var c chan int   c的默认值位nil,一般不使用 
	c := make(chan int)
	c <- 1 //发数据
	n := <-c //收数据 

但是上面收发数据的写法是错误的,因为一个协程往管道里发送数据,必须要有一个协程来接受数据,否则会造成死锁。(协程往管道发送数据和从管道接受数据为了防止数据紊乱,会加上锁)

注意:

        协程从管道里接收数据时,当没有收到数据时,会一直等待。这样效率不是很高,使用select,可以实现同步的功能,后面有介绍。

正确写法:

package main

import (
	"fmt"
	"time"
)

func chanDemo() 
	c := make(chan int)
	go func() 
		for 
			n := <-c //收数据
			fmt.Println(n)
		
	()
	c <- 1 //发数据
	c <- 2

	//防止数据还没有在协程里打印,main函数退出
	time.Sleep(time.Millisecond)


func main() 
	chanDemo()

2. channel作为参数

        channel在go语言里作为一等公民,可以作为参数和返回值

package main

import (
	"fmt"
	"time"
)

func worker(c chan int) 
	for 
		n := <-c
		fmt.Println(n)
	


func chanDemo() 
	var channels [10]chan int
	for i := 0; i < 10; i++ 
		channels[i] = make(chan int)
		go worker(channels[i])
	

	for i := 0; i < 10; i++ 
		channels[i] <- i
	

	//防止数据还没有在协程里打印,main函数退出
	time.Sleep(time.Second)


func main() 
	chanDemo()

3. channel做返回值

package main

import (
	"fmt"
	"time"
)

func worker(id int, c chan int) 
	for 
		fmt.Printf("worker %d, reseived %d\\n", id, <-c)
	


//返回一个channel
func createWoker(id int) chan int 
	c := make(chan int)
	go worker(id, c)
	return c


func chanDemo() 
	var channels [10]chan int
	for i := 0; i < 10; i++ 
		channels[i] = createWoker(i)
	

	for i := 0; i < 10; i++ 
		channels[i] <- i
	

	//防止数据还没有在协程里打印,main函数退出
	time.Sleep(time.Second)


func main() 
	chanDemo()

但是在实际应用中,当项目很复杂时,createWoker返回的channel需要告诉调用方,返回的channel该如何使用,我们可以在返回的channel加上限制。如下:

//返回一个channel,只能被接收数据
func createWoker(id int) <-chan int 
	c := make(chan int)
	go worker(id, c)
	return c


//返回一个channel,只能发送数据
func createWoker(id int) chan<- int 
	c := make(chan int)
	go worker(id, c)
	return c

Channel缓存

        上面说,当一个协程向channel发送数据时,必须要有一个协程来接收数据,否则会造成死锁。

package main

import (
	"fmt"
	"time"
)

func channel() 
	c := make(chan int)
	c <- 1


func main() 
	channel()

         但是我们可以建立一个channel缓存,先将数据放到缓存中,需要时再使用,即使,没有协程接收也不会造成死锁。

package main

import (
	"fmt"
	"time"
)

func bufferedChannel() 
	//channel缓存
	c := make(chan int, 3)

	c <- 1
	c <- 2
	c <- 3


func main() 
	bufferedChannel()

 关闭channel,并不是销毁

        当发送完数据,channel是可以被关闭的。

package main

import (
	"fmt"
	"time"
)

func worker(id int, c chan int) 
	for 
		fmt.Printf("worker %d, reseived %d\\n", id, <-c)
	


func closeChannel() 
	c := make(chan int)
	go worker(0, c)
	c <- 1
	c <- 2
	c <- 3
	close(c)

	time.Sleep(time.Millisecond)


func main() 
	bufferedChannel()

        当发送方关闭channel后,接收方还是可以从channel接收到数据,接收到的数据是后面类型的默认值。

         但是,这样是不合理的,接收方同样可以判断发送方是否关闭了channel

        有两种方法可以判断发送方是否关闭了channel

方法一:利用判断。

package main

import (
	"fmt"
	"time"
)

func worker(id int, c chan int) 
	for 
		n, ok := <-c
		//说明发送方关闭了channel
		if !ok 
			break
		
		fmt.Printf("worker %d, reseived %d\\n", id, n)
	


func closeChannel() 
	c := make(chan int)
	go worker(0, c)
	c <- 1
	c <- 2
	c <- 3
	close(c)

	time.Sleep(time.Millisecond)


func main() 
	closeChannel()

 方法二:利用range,当发送方关闭了channel,range也会自动退出。

package main

import (
	"fmt"
	"time"
)

func worker(id int, c chan int) 
	for n := range c 
		fmt.Printf("worker %d, reseived %d\\n", id, n)
	


func closeChannel() 
	c := make(chan int)
	go worker(0, c)
	c <- 1
	c <- 2
	c <- 3
	close(c)

	time.Sleep(time.Millisecond)


func main() 
	closeChannel()

以上是关于go语言管道(channel)的主要内容,如果未能解决你的问题,请参考以下文章

7.2 什么是Go语言中的管道Channel

7.2 什么是Go语言中的管道Channel

go语言管道(channel)

go语言管道(channel)

Golang channel源码分析

Nil Channels Always Block(Go语言中空管道总是阻塞)