golang管道chan


package main

import (
	"fmt"
	"time"
)

func main() {
	//test2()
	test4()
	time.Sleep(time.Second)

}
func test1() {
	//这个地方注意make的时候指定了管道的容量12,假如说只是向管道里面塞数据不取的话
	//最多塞12个数据就堵塞住了。如果一边塞数据一边取数据,保证管道中始终有空闲位置的话
	//那就可以一直塞数据不会陷入阻塞状态
	queue := make(chan int, 12)
	//起一个协程,不断循环从管道中取数据,如果管道中没有数据就会陷入阻塞状态
	go func() {
		for {
			data := <-queue
			fmt.Println(data, " ")
		}
	}()

	for i := 0; i < 15; i++ {
		queue <- i
	}
}

func test2() {
	/*
		管道的写入或者读取可能会阻塞当前协程,因为当前管道是可读还是可写的是不知道的。
		有可能当前管道缓冲区已经满了协程还尝试写数据,那就写阻塞;也有可能当前缓冲区为空没有数据,协程还尝试读取数据,那就读阻塞。
		这里可以采用golang中的select关键字,同时监听多个管道,非常类似于IO多路复用中的概念,比如epoll。
		轮询的方式监听管道,并从管道中读取数据,这样避免阻塞情况的发生。
	*/
	//搞两个协程,分别往不同的通道写入数据,另有一个主协程轮询从通道中读取数据
	c1 := make(chan int, 2)
	c2 := make(chan int, 2)
	//两个协程异步向两个不同的管道中写入数据
	go func() {
		for i := 0; i < 1000; i++ {
			c1 <- i
			//这个地方设置睡眠可能是为了等待主协程执行,轮询取数据,避免一直向管道中塞数据,可能会塞满陷入到写阻塞
			//time.Sleep(time.Second)//
		}
	}()

	go func() {
		for i := 1000; i < 2000; i++ {
			c2 <- i
			//time.Sleep(time.Millisecond * 500)
		}
	}()

	//for循环结合select语句监听管道数据,轮询的方式,读协程不会再阻塞了(default)
	for {
		select {
		case data := <-c1:
			fmt.Println("data from c1: ", data)
		case data := <-c2:
			fmt.Println("data from c2: ", data)
		default: //当其他分支阻塞的时候,默认执行default

		}
	}
}

func test3() {
	queue := make(chan int, 0) //无缓冲管道(直接读会阻塞)
	//设置定时器1秒后触发,触发后相当于解除阻塞,返回可读管道
	t := time.After(time.Second)
	go func() {
		select {
		case <-queue:
			fmt.Println("从管道取数据正常")
		case <-t: //如果select轮询管道中没有数据可取会进入阻塞状态,但是1秒后定时器触发相当于解除阻塞
			fmt.Println("timeout") //time.After返回的其实就是管道。1秒后管道t 变为可读
		}
	}()
	time.Sleep(time.Second * 3)
}

func test4() {
	/*
		管道的声明一般包含传递的数据类型,但是在某些场景下,我们使用管道只想传递一个信号。
		比如用程序计数器的话我们并不关心管道t读取的数据,而是关心当前管道是否处于可读或者可写的状态(避免阻塞)

		像下面这个程序,主协程要等待子协程运行结束后再退出。这里通过管道实现,管道声明为chan struct{},因为数据不重要了,只关注可读可写状态。
		初始的时候主协程读管道数据陷入阻塞,而等到子协程执行完毕后,向管道中写入数据,主协程就会解除阻塞恢复执行。
		当然这里管道缓冲区为0,也就是写入一个数据必须马上取走,否则连续写入数据到管道同样阻塞
	*/
	queue := make(chan struct{}, 0)
	go func() {
		queue <- struct{}{} //子协程写入数据到管道
		//time.Sleep(time.Second)
	}()
	<-queue //主协程读数据陷入阻塞,等待写协程写入数据到管道后解除阻塞
	fmt.Println("time end")
}

管道chan的底层数据结构:

1、存储数据-->循环队列-->数组(unsafe.Pointer)

2、存储阻塞的协程队列-->管道变量-->sudog

3、协程间并发-->锁mutex(互斥锁控制)

// runtime/chan.go
type hchan struct {
    //当前管道存储的元素数目
    qcount   uint           // total data in the queue
    //管道容量
    dataqsiz uint           // size of the circular queue
    //数组(循环队列做数据缓冲区)
    buf      unsafe.Pointer // points to an array of dataqsiz elements

    //标识管道是否被close
    closed   uint32

    //管道存储的元素类型 & 元素大小
    elemtype *_type // element type
    elemsize uint16

    //读/写索引,循环队列
    sendx    uint   // send index
    recvx    uint   // receive index

    //读阻塞协程队列,写协程堵塞队列
    recvq    waitq  // list of recv waiters
    sendq    waitq  // list of send waiters

    //锁 互斥锁
    lock mutex
}

golang管道chan_第1张图片

参考:【2-5 Golang】Go并发编程—管道chan - Go语言中文网 - Golang中文社区 

你可能感兴趣的:(golang协程,golang)