第八章(并发)[二]

通道

  • Go鼓励使用CSP(Communicating Sequential Process)

  • 作为CSP核心,Channel是显式的,要求操作双方必须知道数据类型和具体通道,并不关心另一端操作者的身份和数量。如果另一端未准备妥当,或消息未能及时处理时,会阻塞当前端

  • 从底层实现上来说,通道只是一个队列。

  • 同步模式下,发送和接收双方配对,然后直接复制数据给对方。如配对失败,则置入等待队列,直到另一方出现后被唤醒。

  • 异步模式下,抢夺的是数据缓冲槽。发送方要求有空槽可供写入,而接收方则要求有缓冲数据可读。 需求不符时,同样加入等待队列,直到另一方写入数据或腾出空槽后被唤醒

  • 除传递消息外(数据)外,通道还常被用做事件通知。

func main() {
	done := make(chan struct{})
	c := make(chan string)

	go func() {
		s := <-c //接收消息
		println(s)
		close(done) //关闭通道,作为结束通知
	}()
	c <- "Hi" //发送消息
	<-done    //阻塞,直到有数据或关闭
}

同步模式必须有配对操作的goroutine,否则会一直阻塞

  • 异步模式在缓冲区未满或数据未读完前,不会阻塞
func main() {
	c := make(chan int, 3)

	c <- 1
	c <- 2

	println(<-c)
	println(<-c)
}


多数时候,异步通道有助于提升性能,减少排队阻塞

  • 缓冲区大小是通道的内部属性,不属于类型的组成部分。

  • 通道变量本身就是指针,可用相等操作符判断是否为同一对象或nil

func main() {
	var a, b = make(chan int, 3), make(chan int)
	var c chan bool
	println(a == b)                            //false
	println(c == nil)                          //true
	fmt.Printf("%v,%T\n", a, a)                //0xc0000d4080,chan int
	fmt.Printf("%v,%T\n", b, b)                //0xc000086060,chan int
	fmt.Printf("%p,%d\n", a, unsafe.Sizeof(a)) //0xc0000d4080,8
	fmt.Printf("%p,%d\n", b, unsafe.Sizeof(b)) //0xc000086060,8
}

  • 内部函数caplen返回缓冲区大小和当前已缓冲数量

  • 对于同步通道则返回0。据此可判断通道是同步还是异步

  • 除使用简单的发送或接收操作符外,还可用ok-idom或range模式处理数据

func main() {
	done := make(chan struct{})
	c := make(chan int)

	go func() {
		defer close(done)

		for {
			x, ok := <-c //判断通道是否关闭
			if !ok {
				return
			}
			println(x)
		}
	}()

	c <- 1
	c <- 2
	c <- 3
	close(c)

	<-done
}

使用 ok-idom判断通道是否关闭

  • 通知可以是群体性的,
func main() {
	var wg sync.WaitGroup
	ready := make(chan struct{})

	for i := 0; i < 3; i++ {
		wg.Add(1)

		go func(id int) {
			defer wg.Done()
			println(id, ": ready.")
			<-ready
			println(id, ": running...")
		}(i)

	}

	time.Sleep(time.Second)
	println("Ready? Go!")
	close(ready)
	wg.Wait()
}
  • 对于closed或nil通道,发送和接收操作都有相应规则:

    • 向已关闭通道发送数据,引发panic
    • 从已关闭通道接收数据,返回已缓冲数据或零值
    • 无论收发,nil通道都会阻塞
func main() {
	c := make(chan int, 3)

	c <- 10
	c <- 20
	close(c)
	for i := 0; i < cap(c); i++ {
		x, ok := <-c
		println(i, ":", ok, x)
	}
}

内建函数close关闭信道,该通道必须为双向的或只发送的。它应当只由发送者执行,而不应由接收者执行,其效果是在最后发送的值被接收后停止该通道。在最后的值从已关闭的信道中被接收后,任何对其的接收操作都会无阻塞的成功。

  • 重复关闭,或关闭nil通道都会引发panic错误(panic: close of closed channel | close of nil channel)
单向通道
  • 通道默认是双向的,并不区分发送端和接收端

  • 尽管可用make创建单向通道,但那没有意义。

  • 通常使用类型转换来获取单向通道,并分别赋予操作双方

func main() {
	var wg sync.WaitGroup
	wg.Add(2)

	c := make(chan int)
	var send chan<- int = c  //只写通道
	var recv <-chan int = c  //只读通道

	go func() {
		defer wg.Done()

		for x := range recv {
			println(x)
		}
	}()

	go func() {
		defer wg.Done()
		defer close(c)

		for i := 0; i < 3; i++ {
			send <- i
		}
	}()

	wg.Wait()
}
  • 不能在单向通道上做逆向操作
func main() {
	c := make(chan int, 2)

	var send chan<- int = c
	var recv <-chan int = c
	<-send    //invalid operation: <-send (receive from send-only type chan<- int)
	recv <- 1 //invalid operation: recv <- 1 (send to receive-only type <-chan int)
}
  • close不能用于接收端(还记得上面案例中:当close通道后,当接收端读取数据完毕后才真正关闭通道)
func main() {
	c := make(chan int, 2)
	var recv <-chan int = c

	close(recv) //invalid operation: close(recv) (cannot close receive-only channel)
}
  • 无法将单向通道转换双向通道

select

  • 如果同时处理多个通道,可选用select语句(它会随机选择一个可用通道做收发操作)
func main() {

	var wg sync.WaitGroup
	wg.Add(2)

	a, b := make(chan int), make(chan int)

	go func() {
		defer wg.Done()
		for {
			var (
				name string
				x    int
				ok   bool
			)
			select {
			case x, ok = <-a:
				name = "a"
			case x, ok = <-b:
				name = "b"
			}

			if !ok { //任何通道关闭,则终止接收
				return
			}
			println(name, x) //输出接收的数据信息

		}

	}()

	go func() {
		defer wg.Done()
		defer close(a)
		defer close(b)

		for i := 0; i < 10; i++ {
			select { //随机选择发送channel
			case a <- i:
			case b <- i * 10:
			}
		}
	}()
	wg.Wait()
}
  • 如果要等全部通道消息处理结束,可将已完成通道设置为nil。这样它就会被阻塞并不再被select选中
func main() {

	var wg sync.WaitGroup
	wg.Add(3)

	a, b := make(chan int), make(chan int)

	go func() {
		defer wg.Done()
		for {

			select {
			case x, ok := <-a:

				if !ok {
					a = nil
					break
				}

				println("a", x) //输出接收的数据信息
			case x, ok := <-b:
				if !ok {
					b = nil
					break
				}
				println("b", x)
			}

			if nil == a && nil == b {
				return
			}

		}

	}()

	go func() {
		defer wg.Done()
		defer close(a)

		for i := 0; i < 3; i++ {
			a <- i

		}
	}()
	go func() {
		defer wg.Done()
		defer close(b)

		for i := 0; i < 5; i++ {
			b <- i * 10

		}
	}()
	wg.Wait()
/*	
a 0
	a 1
	a 2
	b 0
	b 10
	b 20
	b 30
	b 40
*/

}



  • 即便是同一通道,也会随机选择case执行

  • 当所有通道都不可用时,select会执行default语句。(可避开select阻塞)

posted @ 2023-01-30 23:19  巴达克  阅读(16)  评论(0编辑  收藏  举报