app开发定制公司Go中的channel

channel

Goapp开发定制公司语言中的通道(channel)app开发定制公司是一种特殊的类型。
app开发定制公司在任何时候,app开发定制公司同时只能有一个 goroutine app开发定制公司访问通道进行发送和获取数据。goroutine app开发定制公司间通过通道就可以通信。

app开发定制公司通道像一个传送带或者,app开发定制公司总是遵循先入先出(First In First Out)的规则,app开发定制公司保证收发数据的顺序。

(1)channelapp开发定制公司本身是一个队列,先进先出
(2),不需要加锁
(3)本身是有类型的,string, int 等,如果要存多种类型,则定义成 interface类型
(4)channel是引用类型,必须make之后才能使用,一旦 make,它的容量就确定了,不会动态增加!!它和map,不一样

特点:
(1)一旦初始化容量,就不会改变了。
(2)当写满时,不可以写,取空时,不可以取。
(3)发送将持续阻塞直到数据被接收
把数据往通道中发送时,如果接收方一直都没有接收,那么发送操作将持续阻塞。Go 程序运行时能智能地发现一些永远无法发送成功的语句并做出提示
(4)接收将持续阻塞直到发送方发送数据。
如果接收方接收时,通道中没有发送方发送数据,接收方也会发生阻塞,直到发送方发送数据为止。
(5)每次接收一个元素。
通道一次只能接收一个数据元素。

1、关于 channel的声明和使用的代码:

package mainimport (	"fmt")func main() {	//演示一下管道的使用	//1. 创建一个可以存放3个int类型的管道	var intChan chan int	intChan = make(chan int, 3)	//2. 看看intChan是什么	fmt.Printf("intChan 的值=%v intChan本身的地址=%p\", intChan, &intChan)	//3. 向管道写入数据	intChan<- 10	num := 211	intChan<- num	intChan<- 50	// //如果从channel取出数据后,可以继续放入	<-intChan	intChan<- 98//注意点, 当我们给管写入数据时,不能超过其容量	//4. 看看管道的长度和cap(容量)	fmt.Printf("channel len= %v cap=%v \", len(intChan), cap(intChan)) // 3, 3	//5. 从管道中读取数据	var num2 int	num2 = <-intChan 	fmt.Println("num2=", num2)	fmt.Printf("channel len= %v cap=%v \", len(intChan), cap(intChan))  // 2, 3	//6. 在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告 deadlock	num3 := <-intChan	num4 := <-intChan	//num5 := <-intChan	fmt.Println("num3=", num3, "num4=", num4/*, "num5=", num5*/)}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
fmt.Printf("intChan 的值=%v intChan本身的地址=%p\", intChan, &intChan)
  • 1

这句代码显示:channel其实和指针一样,本身存放在一个内存单元中,有它的地址,而它的值是一个 int类型的地址。

2、注意空接口类型的 channel

package mainimport (	"fmt")type Cat struct {	Name string	Age int}func main() {	//定义一个存放任意数据类型的管道 3个数据	//var allChan chan interface{}	allChan := make(chan interface{}, 3)	allChan<- 10	allChan<- "tom jack"	cat := Cat{"小花猫", 4}	allChan<- cat	//我们希望获得到管道中的第三个元素,则先将前2个推出	<-allChan	<-allChan	newCat := <-allChan //从管道中取出的Cat是什么?	fmt.Printf("newCat=%T , newCat=%v\", newCat, newCat)	//下面的写法是错误的!编译不通过	//fmt.Printf("newCat.Name=%v", newCat.Name)	//使用类型断言	a := newCat.(Cat) 	fmt.Printf("newCat.Name=%v", a.Name)}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

定义 interface类型的空接口,可以接收任意类型的数据,但是在取出来的时候,必须断言!
a := newCat.(Cat)

3、channel的关闭:close( )

关闭之后,不能再写入,只能读。
只能由发送者执行这句代码

4、channel的遍历: for … range

通道的数据接收一共有以下 4 种写法。

  1. 阻塞接收数据
    阻塞模式接收数据时,将接收变量作为<-操作符的左值,格式如下:
data := <-ch
  • 1

执行该语句时将会阻塞,直到接收到数据并赋值给 data 变量。

  1. 非阻塞接收数据(有问题啊,还是会报错deadlock
    使用非阻塞方式从通道接收数据时,语句不会发生阻塞,格式如下:
data, ok := <-ch
  • 1

data:表示接收到的数据。未接收到数据时,data 为通道类型的零值。
ok:表示是否接收到数据。

非阻塞的通道接收方法可能造成高的 CPU 占用,因此使用非常少。如果需要实现接收超时检测,可以配合 select 和计时器 channel进行,可以参见后面的内容。

  1. 接收任意数据,忽略接收的数据
    阻塞接收数据后,忽略从通道返回的数据,格式如下:
<-ch
  • 1

执行该语句时将会发生阻塞,直到接收到数据,但接收到的数据会被忽略。这个方式实际上只是通过通道在 goroutine 间阻塞收发实现并发同步。

使用通道做并发同步的写法,可以参考下面的例子:

package mainimport (    "fmt")func main() {    // 构建一个通道    ch := make(chan int)    // 开启一个并发匿名函数    go func() {        fmt.Println("start goroutine")        // 通过通道通知main的goroutine        ch <- 0        fmt.Println("exit goroutine")    }()    fmt.Println("wait goroutine")    // 等待匿名goroutine    <-ch    fmt.Println("all done")}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  1. 循环接收
    通道的数据接收可以借用 for range 语句进行多个元素的接收操作,格式如下:
for data := range ch {}
  • 1
  • 2

通道 ch 是可以进行遍历的,遍历的结果就是接收到的数据。数据类型就是通道的数据类型。通过 for 遍历获得的变量只有一个,即上面例子中的 data。

package mainimport (	"fmt")func main() {	intChan := make(chan int, 3)	intChan<- 100	intChan<- 200	close(intChan) // close	//这时不能够再写入数到channel	//intChan<- 300	fmt.Println("okook~")	//当管道关闭后,读取数据是可以的	n1 := <-intChan	fmt.Println("n1=", n1)	//遍历管道	intChan2 := make(chan int, 100)	for i := 0; i < 100; i++ {		intChan2<- i * 2  //放入100个数据到管道	}	//遍历管道不能使用普通的 for 循环	// for i := 0; i < len(intChan2); i++ {	// }	//在遍历时,如果channel没有关闭,则会出现deadlock的错误	//在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历	close(intChan2)	for v := range intChan2 { //没有下标		fmt.Println("v=", v)	}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

在遍历管道之前要先关闭管道,不然会出现deadlock的错误

应用1

开两个管道;
当写协程完成工作之后,close数据管道,读协程对数据管道 intChan的数据读完之后,就向退出管道 exitChan 写入一个 true,close掉

主线程循环检测退出管道里是否有数据,如果有,说明读协程完成,主程序就可以退出了。

package mainimport (	"fmt")//write Datafunc writeData(intChan chan int) {	for i := 1; i <= 50; i++ {		//放入数据		intChan <- i //		fmt.Println("writeData ", i)		//time.Sleep(time.Second)	}	close(intChan) //关闭}//read datafunc readData(intChan chan int, exitChan chan bool) {	for {		v, ok := <-intChan		if !ok {			break		}		// time.Sleep(time.Second)		fmt.Printf("readData 读到数据=%v\", v)	}	//readData 读取完数据后,即任务完成	exitChan <- true	close(exitChan)}func main() {	//创建两个管道	intChan := make(chan int, 10)	exitChan := make(chan bool, 1)	go writeData(intChan)	go readData(intChan, exitChan)	//time.Sleep(time.Second * 10)	for {		_, ok := <-exitChan		if !ok {			break		}	}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

应用2



定义三个管道:
intChan :放8000个数
primeChan:放素数
exitChan :4个协程运行完毕的标志

package mainimport (	"fmt"	"time")//向 intChan放入 1-8000个数func putNum(intChan chan int) {	for i := 1; i <= 80000; i++ {		intChan <- i	}	//关闭intChan	close(intChan)}// 从 intChan取出数据,并判断是否为素数,如果是,就// 	//放入到primeChanfunc primeNum(intChan chan int, primeChan chan int, exitChan chan bool) {	//使用for 循环	// var num int	var flag bool //	for {		//time.Sleep(time.Millisecond * 10)		num, ok := <-intChan //intChan 取不到..		if !ok {			break		}		flag = true //假设是素数		//判断num是不是素数		for i := 2; i < num; i++ {			if num%i == 0 { //说明该num不是素数				flag = false				break			}		}		if flag {			//将这个数就放入到primeChan			primeChan <- num		}	}	fmt.Println("有一个primeNum 协程因为取不到数据,退出")	//这里我们还不能关闭 primeChan	//向 exitChan 写入true	exitChan <- true}func main() {	intChan := make(chan int, 1000)	primeChan := make(chan int, 20000) //放入结果	//标识退出的管道	exitChan := make(chan bool, 4) // 4个	start := time.Now().Unix()	//开启一个协程,向 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		}		end := time.Now().Unix()		fmt.Println("使用协程耗时=", end-start)		//当我们从exitChan 取出了4个结果,就可以放心的关闭 prprimeChan		close(primeChan)	}()	//遍历我们的 primeChan ,把结果取出	for {		res, ok := <-primeChan		if !ok {			break		}		//将结果输出		fmt.Printf("素数=%d\", res)	}	fmt.Println("main线程退出")}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
有一个primeNum 协程因为取不到数据,退出有一个primeNum 协程因为取不到数据,退出有一个primeNum 协程因为取不到数据,退出有一个primeNum 协程因为取不到数据,退出使用协程耗时= 3main线程退出
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

存数字和计算素数比较简单,不提
开启4个协程,运算素数,效率比单个线程高几倍!

go func() {		for i := 0; i < 4; i++ {			<-exitChan		}		end := time.Now().Unix()		fmt.Println("使用协程耗时=", end-start)		//当我们从exitChan 取出了4个结果,就可以放心的关闭 prprimeChan		close(primeChan)	}()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这里定义了一个匿名协程,作用是检测4个协程 有没有完成运行,取不出来就会阻塞,等待协程完成。也可以这样:
if len(exitChan) == 4

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发