golang 多返回值和channel的底层原理

golang 返回值和c的底层区别

1、栈帧

栈帧结构的两端由两个指针来指定。寄存器ebp通常用做帧指针(frame pointer),而esp则用作栈指针(stack pointer)。在函数执行过程中,栈指针esp会随着数据的入栈和出栈而移动,因此函数中对大部分数据的访问都基于帧指针ebp进行。

esp和ebp: esp是栈指针,是cpu机制决定的,push、pop指令会自动调整esp的值;ebp只是存取某时刻的esp这个时刻就是进入一个函数内后,cpu会将esp的值赋给ebp,此时就可以通过ebp对栈进行操作,比如获取函数参数,局部变量等,实际上使用esp也可以;

  • EBP:基址指针寄存器(extended base pointer),存放一个指针,永远指向系统栈最上面一个栈帧的底部

  • ESP:栈指针寄存器(extended stack pointer),存放一个指针,永远指向系统栈最上面一个栈帧的栈顶

  • EAX 寄存器也叫做累加寄存器,用于存储函数的返回值外也用于执行计算的 操作。
    在这里插入图片描述

  • 调用函数中push ebp,将main函数的ebp压栈,然后mov ebp, esp将当前函数的esp赋给ebp,得到当前函数的栈底地址。

//函数调用之所以能够返回,单靠保持返回地址是不够的,这一步压栈动作很重要,
//因为我们要标记函数调用者栈帧的帧底,这样才能找出保存了的返回地址,
//栈顶是不用保存的,因为上一个栈帧的顶部会是func的栈帧底部。(两栈帧相邻的)
push ebp; 

//mov指令将esp寄存器的值赋值给ebp寄存器。上一栈帧的顶部,就是这个栈帧的底部
mov ebp, esp; 
  • 调用函数结束之前,执行leave指令,其实该指令等于下面两条指令:此时fun相关数据全部被出栈,ebp将得重新到main函数的栈底地址,esp回到函数栈顶部.
mov esp, ebp //mov 将ebp的值 赋值给esp
pop ebp  //将堆栈中的ebp值弹出

2、go汇编中有4个伪寄存器

  • FP: Frame pointer,指向栈底位置,一般用来引用函数的输入参数,用来访问函数的参数
  • PC: Program counter: 程序计数器,用于分支和跳转
  • SB: Static base pointer: 一般用于声明函数或者全局变量
  • SP: Stack pointer:指向当前栈帧的局部变量的开始位置(栈顶位置),一般用来引用函数的局部变量

在 C 语言中调用一个函数,函数的参数是通过寄存器和栈传递的,在 x86_64 的机器上,6 个以下(含 6 个)的参数会按照顺序分别使用 edi、esi、edx、ecx、r8d 和 r9d 六个寄存器传递,超过 6 个的剩余参数会通过栈进行传递;函数的返回值是通过 eax 寄存器进行传递的,这也就是为什么 C 语言中不支持多个返回值。

Golang 函数参数以及返回值都是通过SP来保存的,那么也就是说是通过堆栈寄存器来保存的。所以可以做到返回多个,因为它跟参数传递使用的都是堆栈。golang的返回参数是放在栈里面的,把返回值的地址存放到栈中,golang函数调用过程,是通过fp+offset来实现传参和返回值,而不像C/C++都是通过寄存器实现传参和返回值。 所以golang能够支持多个参数返回。

+-----------+---\
| 返回值2 | \
+-----------+  \
| 返回值1 |  \
+---------+-+  
| 参数2 |  这些在调用函数中
+-----------+  
| 参数1 |   /
+-----------+  /
| 返回地址 | /
+-----------+--\/-----fp值
| 局部变量 | \
| ... | 被调用数栈祯
|   | /
+-----------+--/+---sp值

golang channel的底层实现

1、chan的实现在runtime/chan.go中,channel底层是一个hchan的结构体,根据有无缓冲区分为:有缓冲channel和无缓冲channel.

make(chan int)   // 无缓存 chan
make(chan int, 10)  // 有缓存 chan
//底层数据结构
type hchan struct {
  buf      unsafe.Pointer // 存放实际数据的指针,用unsafe.Pointer存放地址,为了避免gc
  qcount   uint           // 队列中的数据个数
  dataqsiz uint           // 环形队列的大小,channel本身是一个环形队列
  elemsize uint16 
  closed   uint32 // 标识channel是否关闭
  elemtype *_type // 数据 元素类型
  sendx    uint   // send的 index
  recvx    uint   // recv 的 index
  recvq    waitq  // 阻塞在 recv 的队列
  sendq    waitq  // 阻塞在 send 的队列
  
  lock mutex  // 锁 
}

  • channel本身是一个环形缓冲区,数据存放到堆上面,channel的同步是通过锁实现的。channel中有两个队列,一个是发送阻塞队列,一个是接收阻塞队列。当向一个已满的channel发送数据会被阻塞,此时发送协程会被添加到sendq中,同理,当向一个空的channel接收数据时,接收协程也会被阻塞,被置入recvq中。
  • buf是有缓冲的channel所特有的结构,用来存储缓存数据。是个循环链表
  • sendxrecvx用于记录buf这个循环链表中的发送或者接收的index
  • lock是个互斥锁。
  • recvqsendq分别是接收(<-channel)或者发送(channel <- xxx)的goroutine抽象出来的结构体的队列。是个双向链表。

2、send(channel <- xxx)/recv(<-channel)的操作的细节可以细化为:

  • 第一,加锁
  • 第二,把数据从goroutine中copy到“队列”中(或者从队列中copy到goroutine中)。
  • 第三,释放锁

3、当 channel 缓存满了,或者没有缓存的时候,我们继续 send(ch <- xxx) 或者 recv(<- ch) 会阻塞当前 goroutine。

  • 阻塞的发送协程G1会被抽象成含有 G1 指针和 send 元素的sudog结构体保存到 hchan 的sendq中等待被唤醒.

  • G2从缓存队列中取出数据,channel会将等待队列中的G1推出,将G1当时send的数据推到缓存中,然后调用Go的scheduler,唤醒G1,并把G1放到可运行的Goroutine队列中。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Go语言中,channel是一种用于goroutine之间通信的重要机制。它可以在goroutine之间进行同步和数据传递。Channel可以被看作是一种先进先出的队列,每个元素都是一个数据类型。 Channel的实现原理基于一种叫做CSP(Communicating Sequential Processes)的并发模型,该模型是由Tony Hoare提出的。CSP模型中,进程通过发送和接收消息来进行通信和同步。 Go语言中的channel也是基于CSP模型实现的,其底层是通过一个管道(pipe)来实现的。管道是一种特殊的文件,可以通过文件操作来进行读写,但是它不同于普通的文件,它是一种特殊的文件,只能用于进程间通信,而不是用于存储数据。 当我们创建一个channel时,实际上是创建了一个管道,该管道有两端,一个用于发送数据,一个用于接收数据。goroutine可以通过channel向管道中写入数据或从管道中读取数据,当管道中没有数据时,读取操作会被阻塞,直到有数据写入管道。同样地,当管道已满时,写入操作也会被阻塞,直到有数据从管道中被读取。 在Go语言中,channel有两种类型:有缓冲和无缓冲。无缓冲的channel可以保证数据的即时传输,当数据被写入channel时,写入操作会被阻塞,直到有一个goroutine从channel中读取了数据。有缓冲的channel可以缓存一定量的数据,当缓存满时,写入操作会被阻塞,直到有一个goroutine从channel中读取了数据,同样地,在缓存未满时,读取操作会被阻塞,直到有数据被写入到channel中。 总之,channel是Go语言中非常重要的并发机制,它可以保证多个goroutine之间的同步和通信。它的实现原理基于CSP模型,底层是通过管道来实现的。了解channel原理对于理解Go语言的并发编程非常重要。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值