go-channel实现

channel入门

channel基础

goroutines之间的通信,让它们之间可以进行数据交换。
像管道一样,一个goroutine_A向channel中放数据,另一个goroutine_B从channel取数据。
(在放和取的过程中,有互斥保证、hapen before保证,具体见unbuffer管道和buffer管道的阻塞现象)

定义及类型

ch := make(chan int) // unbuffer的chan
ch := make(chan string) //buffer的chan
chan Type表示chan中元素的类型,用<-chan Type 和 chan<- Type表示只读管道和只发管道

操作形式:
ch <- Value // 发送
<-ch //读取
val := <-ch
val, ok = <ch

属性和分类

操作

  • send,发送数据
  • receive,接收数据
  • close,关闭
    关闭后,send操作导致panic;
    关闭后,recv操作时,如果有未读的数据则获取到未读数据,否则返回对应类型0值和状态码false。可利用这个性质做一些其他操作;
    并非强制需要close来关闭,有时会自动关闭;
    一般send端使用close,表示无数据再发送;

分类

unbuffered channel和buffered channel
unbuffered: 阻塞、同步模式
send发送数据,然后阻塞,直到receive端将此数据接收
receive一直阻塞,直到send端发送了一个数据
buffered: 非阻塞、异步
send端在容量未满时发送数据,不会阻塞
receive按FIFO从中取数据
可以认为,send给unbuffer的chan,必须等有接收者才返回;给buffer的chan,
容量未满时暂存到内部直接返回。而receive都是必须有数据才不阻塞,
而unbuffer有数据意味着有send,buffered的有数据意味中当前容量不为空。

buffered chan两个属性

容量(cap)和长度(len)
cap表示最多可以缓存多少数据
len表示已缓存数量

两种特殊chan

nil chan和chan类型chan
nil chan: 只声明未分配内存,如var ch chan int
nil channel会永远阻塞对该channel的读、写操作。
chan类型chan: 双层通道, chan的元素类型也是chan

死锁

所有的go routine都被阻塞了,就称之为死锁。
例如在主main中,声明了一个unbuffered chan,如果只做send或receive操作,则这个主main
就阻塞了,这时就会报错。
也就是说,为了保证主main中send或receive不阻塞,必须在它操作前有其他的go routine实现了
与它相反的receive或send。
使用unbufferd chan进行通信

func testChan(name string, wg *sync.WaitGroup, ch chan int) {
	defer wg.Done()
	for {
		value, ok := <-ch
		if !ok {
			fmt.Printf("go routine[%v] receive close signal\n", name)
			return
		}
		if value == 10 {
			close(ch)
			fmt.Printf("go routine[%v] send close signal\n", name)
			return
		}
		value++
		ch <- value
	}
}

func main() {
	var wg sync.WaitGroup
	ch := make(chan int)

	wg.Add(2)

	go testChan("name1", &wg, ch)
	go testChan("name2", &wg, ch)

	ch <- 1

	wg.Wait()
	fmt.Printf("main end %v\n", "aaa")
}

这里ch <- 1必须放在go testChan后面,

使用for range迭代chan

前面都是在for无限循环中读取channel中的数据,但也可以使用range来迭代channel,它会返回每次迭代过程中所读取的数据,直到channel被关闭。
必须注意,只要channel未关闭,range迭代channel就会一直被阻塞。

func testChan(name string, wg *sync.WaitGroup, ch chan int) {
	defer wg.Done()
	for value := range ch {
		if value == 10 {
			close(ch)
			fmt.Printf("go routine[%v] send close signal\n", name)
			return
		}
		value++
		ch <- value
	}
}

多个管道数据传递

将数据从A读入,写入B,从B在读入,写到C…可以实现多线程按指定顺序打印。

func recvAndSendNext(name string, recv_ch chan int, send_ch chan int) {
	value := <-recv_ch
	value++
	send_ch <- value
}
func main() {
		ch1 := make(chan int)
	ch2 := make(chan int)
	ch3 := make(chan int)
	ch4 := make(chan int)

	//wg.Add(1)


	go recvAndSendNext("name1", ch1, ch2)
	go recvAndSendNext("name2", ch2, ch3)
	go recvAndSendNext("name2", ch3, ch4)
	ch1 <- 0




	//wg.Wait()
	data := <- ch4

	fmt.Printf("main end %v\n", data)
}

buffered channel异步队列请求示例

使用buffered chan, 在缓存未满时不会阻塞

type Task struct {
	ID int
	Status string
}

func (t *Task) run() {
	time.Sleep(2 * time.Second)
	t.Status = "Completed"
}
func woker(poll_chan chan Task, i int, wg *sync.WaitGroup) {
	defer wg.Done()
	for{
		task, ok := <- poll_chan
		if ok {
			task.run()
		}else {
			return
		}
	}
}

func main() {
		chan_pool := make(chan Task, 10)  // 容量为10的chan

	var wg sync.WaitGroup

	// 先启动线程,不然主线程可能会阻塞
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go woker(chan_pool, i, &wg) // 从chan_pool中取数据
	}

	// 启动线程后,发送数据
	for j := 0; j < 20; j++ {
		chan_pool <- Task{ID: j, Status: "Ready"}
	}

	close(chan_pool)

	wg.Wait()

	fmt.Printf("main end")
}

select多路监听

同时监听多个事件,比如多个channel的可读、可写监听

select {
	// ch1有数据时,读取到v1变量中
	case v1 := <-ch1:
		...
	// ch2有数据时,读取到v2变量中
	case v2 := <-ch2:
		...
	// 所有case都不满足条件时,执行default
	default:
		...
}

default语句,都所有语句阻塞时,会执行default,因此default必须要可执行而不能阻塞
多个case语句同时满足条件,则随机选择一个并退出select。
select的case中也可以有send操作,同样也是可以send时该case语句才认为有效。

time.After()

func After(d Duration) <-chan Time
time.After()返回一个只读chan, 注意该函数是立即返回的,不是阻塞等到Duratio时间才返回。但它是Duration后才向chan中写入完成时所处时间点。
因此将case语句中判断它是否可读,就作为了超时机制

select {
	case val := <-ch1:
		fmt.Println("recv value from ch1:",val)
		return
	// 只等待3秒,然后就结束
	case <-time.After(3 * time.Second):
		fmt.Println("3 second over, timeover")
	}
// 源码
time.After(1 * time.Second)

func After(d Duration) <-chan Time {
	return NewTimer(d).C
}

func NewTimer(d Duration) *Timer {
	c := make(chan Time, 1)     // 创建一个chan
	t := &Timer{
		C: c,
		r: runtimeTimer{
			when: when(d),
			f:    sendTime,
			arg:  c,
		},
	}
	startTimer(&t.r)
	return t
}

func startTimer(*runtimeTimer)

time.Tick()

Tick(d)则是间隔地多次等待,每次等待d时长,并在每次间隔结束的时候将当前时间发送到通道。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值