go中的channel

channel用于携程中的通信
go中的channel是一个队列,遵循先进先出的原则,负责携程之间的通信(go提倡不要通过共享内存来通信,而是要通过通信来实现内存共享),CSP并发模型就是通过goroutine和channel来实现的

使用场景

停止信号监听
定时任务
生产方和消费方解耦
控制并发数

底层数据结构

通过var声明或者make函数创建的channel变量是一个存储在函数栈帧上的指针,占用8个字节,指向堆上的hchan结构体,该结构体在src/runtime/chan.go中
在这里插入图片描述
buf就是一个缓冲的数组,如果是一个有缓冲的数据,就会使用这个数组,使用循环数组的时候就是消费数据时,其他的元素不需要一定,通过两个下标就可以搞定了
sendq和recvq就是通道在读取或者写入数据的时候,会发生堵塞,所以需要两个队列
lock锁,因为线程是安全的,所以需要一把锁

为什么设计成线程安全?

不同协程通过channel进行通信,本身的使用场景就是多线程,为了保证数据的一致性,必须实现线程安全

如何实现线程安全的?

channel的底层实现中,hchan结构体中采用Mutex锁来保证数据读写安全。在对循环数组buf中的数据进行入队和出队操作时,必须先获取互斥锁,才能操作channel数据

channel如何控制goroutine并发执行顺序

多个goroutine并发执行时,每一个goroutine抢到处理器的时间点不一致,gorouine的执行本身不能保证顺序。即代码中先写的gorouine并不能保证先执行

思路:使用channel进行通信通知,用channel去传递信息,从而控制并发执行顺序

channel共享内存有什么优劣势

优点:使用 channel 可以帮助我们解耦生产者和消费者,可以降低并发当中的耦合
缺点:容易出现死锁的情况

Go channel发送和接收什么情况下会死锁

死锁

  1. 单个协程永久阻塞
  2. 两个或两个以上的协程的执行过程中,由于竞争资源或由于彼此通信而造成的一种阻塞的现象

channel死锁场景

  1. 非缓存channel只写不读
  2. 非缓存channel读在写后面
  3. 缓存channel写入超过缓冲区数量
  4. 空读
  5. 多个协程互相等待

创建时的策略

  1. 如果是无缓冲的channel,会直接给hchan分配内存
  2. 如果是有缓冲的channel,并且元素不包含指针,那么会为hchan和底层数组分配一段连续的地址
  3. 如果是有缓冲的channel,并且元素包含指针,那么会为hchan和底层数组分别分配地址

向channel中发送数据时

  1. 如果channel的读等队列存在接受者goroutine
    将数据直接发送给第一个等待的goroutine,唤醒接收的goroutine
  2. 如果channel的读等队列不存在接受者goroutine
    1. 如果循环数组的buf未满,那么将数据发送到循环数组的队尾
    2. 如果循环数组的buf已满,将当前的goroutine加入写等待对列,并挂起等待唤醒接收

向channel中接收数据时

  1. 如果channel的写等待队列存在发送者goroutine

    1. 如果是无缓冲channel,直接从第一个发送者goroutine那里把数据拷贝给接收变量,唤醒发送的gorontine
    2. 如果是有缓冲channel(已满),将循环数组buf的队首元素拷贝给接受变量,将第一个发送者goroutine的数据拷贝到循环数组队尾,唤醒发送到goroutine
  2. 如果channel的写等待队列不存在发送者goroutine

    1. 如果循环数组buf非空,将循环数据buf的队首元素拷贝给接受变量
    2. 如果循环数组buf为空,这个时候就会走阻塞接收的流程,将当前goroutine加入读等队列,并挂起等待唤醒

无缓冲channel的定义与使用

package main

import (
	"fmt"
)


func main() {
	// 定义一个无缓冲的channel
	c := make(chan int)

	// 定义一个G1
	go func(){
		defer fmt.Println("G1 结束")

		fmt.Println("G1正在运行")

		// 将666 发送给c
		c <- 666
	}()

	// 从c中将666取出并赋值给num
	num := <-c

	fmt.Println("num == ", num)
	fmt.Println("main goroutine结束。。。。。。")


}

有缓冲channel的定义与使用

package main

import (
	"fmt"
	"time"
)


func main() {
	// 定义一个有缓冲的channel
	c := make(chan int, 3)

	fmt.Println("len(c) = ", len(c), "cap(c) = ", cap(c))

	// 定义一个G1
	go func(){
		defer fmt.Println("G1 结束")

		for i := 0; i < 3; i++ {
			c <- i
			fmt.Println("G1正在运行", "len(c) = ", len(c), "cap(c) = ", cap(c))
		}
	}()

	// 确保元素全部放入通道
	time.Sleep(time.Second*2)
	

	for i := 0; i < 3; i++ {
		num := <-c
		fmt.Println("num is ", num)
	}
	fmt.Println("main goroutine结束。。。。。。")
}

通道的关闭close

package main

import (
	"fmt"
)
func main() {
	// 定义一个无缓冲的channel
	c := make(chan int)

	// 定义一个G1
	go func(){
		defer fmt.Println("G1 结束")
		for i := 0; i < 3; i++ {
			c <- i	
		}
		// close可以关闭一个channel
		close(c)
	}()

	for {
		// ok如果为true表示channel没有关闭,反之则表示关闭
		if data, ok := <-c; ok {
			// 如果上面没有close则会死锁
			fmt.Println(data)
		}else{
			break
		}
	}
	fmt.Println("main goroutine结束。。。。。。")
}

使用range来取数据

package main

import (
	"fmt"
)


func main() {
	// 定义一个无缓冲的channel
	c := make(chan int)

	// 定义一个G1
	go func(){
		defer fmt.Println("G1 结束")
		for i := 0; i < 3; i++ {
			c <- i	
		}

		// close可以关闭一个channel
		close(c)
	}()
	
	// 可以使用range来迭代不断操作channel
	for data := range c{
		fmt.Println(data)
	}
	fmt.Println("main goroutine结束。。。。。。")
}

select监控多个channel的状态

package main

import (
	"fmt"
)

func fibonacii(c,quit chan int) {
	x, y := 1, 1
	for{
		select {
		// 如果c通道能够放入数据则进入
		case c <- x:
			x = y
			y = x +y
		// 如果quit通道能够取出数据
		case <-quit:
			fmt.Println("quit")
			return
		}
	}	
}


func main() {
	c := make(chan int)
	quit := make(chan int)

	// sub go
	go func() {
		for i := 0; i < 10; i++ {
			// 从c通道中读取数据
			fmt.Println(<-c)
		}
		// 往quit通道中放入数据
		quit <-0
	}()
	// main go
	fibonacii(c,quit)
	fmt.Println("主携程结束。。。。。。")
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值