golang channel的使用技巧

go的横空出世,让很多人眼前一亮,它的语法以简洁著称,并且它对多核并发的原生支持,让他在云计算和分布式领域展露头脚,它的核心围绕channel和goroutine展开。

首先golang channel 分为有缓冲与无缓冲两种类型,很多人认为无缓冲channel单单只是 默认缓冲为1缓冲的channel,其实它们最大的区别是阻塞问题。如下

c1:=make(chan int)        无缓冲  

c2:=make(chan int,1)      有缓冲

c1<-1                            

在c1<- 1处,因为c1是无缓冲的,因此程序会阻塞在这里,不会往下执行,但是c2则不会,他在channel满之前是不会阻塞的,他会继续往下执行,但是如果你在c2第一个数据未取出之前继续塞第二个数据,就会阻塞在你放第二个数据处,因为她的缓冲区单位只有1.例:

dgucochann := make(chan int,2)   

dgucochann <- 1

fmt.Println("Hello DGuco")

dgucochann <- 2    

fmt.Println("Hello Yuki")  

dgucochann <- 3

fmt.Println("Hello Go")

在这种情况下,程序在第6行处会阻塞,会打印前两句话Hello DGuco和Hello Yuki,因为该channel只有两个单位的缓冲,在第6行时没有另外一个goroutine去取出里面的数据,所以阻塞在这里。

下面我们来介绍一下channel的基本用法。

1 信号量的传递

package main

import (
	"fmt"
	"time"
)

var mychan = make(chan int)

func dgucofunc1() {
	fmt.Println("In dguco_func1")
	mychan <- 1
}

func dgucofunc2() {
	a := <-mychan
	fmt.Println("In dguco_func1")
}
func main() {
	go dgucofunc1()
	go dgucofunc2()
	time.Sleep(time.Millisecond * 10)
}


可以看到依次打出了相应的两段文字,如果把第6行去掉,因为在dgucofunc2收不到来自channel的信号就会阻塞,结果只会打印In dguco_func1
2 生产者消费者问题
package main

import (
	"fmt"
	"time"
)

func dgucoproducer(c chan int, max int) {
	for i := 0; i < max; i++ {
		c <- i
	}
	//close(c)
}

func dgucoconsumer(c chan int) {
	ok := true
	value := 0
	for ok {
		fmt.Println("Wait receive")
		if value, ok = <-c; ok {
			fmt.Println(value)
		}
		if ok == false{
			fmt.Println("*******Break********")
		}
	}
}

func main() {
	c := make(chan int)
	defer close(c)
	go dgucoproducer(c, 10)
	go dgucoconsumer(c)
	time.Sleep(time.Second * 5)
	fmt.Println("Done")
}
这个很简单,执行结果依次打印0-9
3  定时器(这个经常用)
package main

import "time"
import "fmt"

var mychannel = make(chan bool)

func dgucotimertask() {
	fmt.Println("My TimerTask..")
}

func dgucotimer() {
	//返回一个定时器
	mytimer := time.NewTicker(time.Millisecond * 1000)
	select {
	case <-mytimer.C:
		go dgucotimertask()
	}
}

func main() {
	dgucotimer()
	time.Sleep(time.Millisecond * 10)
}
顺便说一下select的作用,首先它就相当于switch,但是他是用于channel的,举个例子:

select{
case <- c1:
.........
case <- c2:
........:
当程序执行到这里时,程序会阻塞在此,直到c1或者c2中有数据被放入就会继续执行,当然一般不会这样写,因为如果c1,和c2永远收不到数据程序阻塞在这里肯定不是我们想要的结果,一般我们都会写一个超时处理。
package main

import "time"
import "fmt"

var mychannel = make(chan bool)

func dgucotimertask() {
	fmt.Println("My TimerTask..")
}

func dgucotimer() {
	timeout := time.NewTicker(time.Millisecond * 10000)

	select {
	case <-mychannel:
		go dgucotimertask()
	case <-timeout.C:
		fmt.Println("Time out")
	}
	fmt.Println("Hello go")
}

func main() {
	dgucotimer()
	time.Sleep(time.Millisecond * 10)
}
这里我们是没有向mychannel中写数据的,但是等到10秒之后会打印出hello go,程序并没有因此阻塞在这里。

4 goroutine 通信
package main

import "time"
import "fmt"

var mychannel = make(chan int)

func dgucoroutine() {
	for i := 0; i < 10; i++ {
		mychannel <- i
	}

}
func dgucoroutine1() {
	for {
		i := <-mychannel
		fmt.Println("dgucoroutine1:", i)
	}
}

func dgucoroutine2() {
	for {
		i := <-mychannel
		fmt.Println("dgucoroutine2:", i)
	}
}

func main() {
	go dgucoroutine()
	go dgucoroutine1()
	go dgucoroutine2()
	// mychannel <- 4
	time.Sleep(time.Millisecond * 100)
}

这里实现了一个goroutine写,两个goroutine读的过程,可以看到按顺序写进去的数据,被哪个goroutine读到是完全随机的,在golang中我们要实现进程间通信,channel是唯一途径,也是推荐的途径,她的底层是通过共享内存实现的,速度非常快,这样我们摆脱了linux系统繁琐,古老的进程间通信方式,管道,消息队列,信号量等,非常的方便。
最后做一个总结,在使用channel的时候,一定要至少有一个goroutine来负责读(除非你有特殊的需求),否则你的程序就会阻塞在你写channel的地方。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值