您现在的位置是:首页 >技术教程 >golang原理网站首页技术教程
golang原理
1. Goroutine
Goroutine 是 Go 语言中的一种轻量级线程,它的实现方式与传统的线程不同。传统的线程调度是由操作系统内核进行的,而 Goroutine 则是由 Go 运行时环境进行调度。Goroutine 的底层原理由三部分组成:栈、调度器和信道。
-
首先,每个 Goroutine 都有自己的栈,栈的大小可以在运行时进行更改。这个栈与普通的线程栈不同,它的大小是动态变化的,可以根据需要进行调整,从而避免了栈溢出的问题。
-
其次,Go 语言的调度器是通过 M(Machine):P(Processor):G(Goroutine) 的模型实现的。其中 M 表示操作系统中的线程。Go 语言的调度器中有多个 P 用于管理多个 Goroutine,每个 P 最多绑定一个 M。Goroutine 正在执行时会与一个 P 绑定在一起,当需要切换 Goroutine 时,调度器会将它与所绑定的 P 分离,并将其绑定到其他 P 上,从而实现 Goroutine 的调度。
-
最后,信道是 Goroutine 之间进行通信的方式,它实现了同步和互斥。信道使得 Goroutine 之间的通信变得简单而又安全,而不必担心死锁和竞态条件的问题。信道的实现方式与管道类似,但有些差异。管道通常是通过复制数据实现通信,而信道则是通过执行发送和接收操作实现通信。
通过栈、调度器和信道的组合,Go 语言实现了高效的 Goroutine 调度和同步机制,使得并发编程变得更加简单和安全。
2. GMP
当一个 P 空闲之后,会按照下面的优先级顺序获取 Goroutine:
-
从 P 的本地队列获取 Goroutine,这是最快的获取 Goroutine 的方式。
-
从全局队列获取 Goroutine,这时需要保证不会出现所有 P 都在使用全局队列,可以通过在全局队列上加锁或使用更高效的数据结构来避免这个问题出现。
-
从其他 P 的本地队列获取 Goroutine,这个代价比较高,需要进行跨 P 的通信。
如果以上三个步骤都获取不到 Goroutine,那么当前 P 就会进入自旋状态,不断尝试获取 Goroutine 或等待其他 P 放入 Goroutine,这时需要保证不会出现无限制的自旋导致 CPU 时间的浪费。
总之,在 GMP 模型中,P 为空闲时会尝试从多个渠道获取 Goroutine,并且优先从 P 的本地队列获取 Goroutine,以保证 Goroutine 的快速调度,从而提高程序的运行效率
3. channel
在 Golang 中,channel 的实现是通过在底层使用信号量和互斥锁来实现的。具体来说,在 channel 内部,会有两个指针来分别表示 channel 的读取和写入位置,同时还会有一个数组来存储 channel 中的数据。
当一个 Goroutine 通过 channel 发送数据时,它会首先获取一个写锁来保证并发安全,并将数据存储到 channel 中,并对写入位置指针进行更新。当一个 Goroutine 通过 channel 接收数据时,它会首先获取一个读锁来保证并发安全,并从 channel 中读取数据,并对读取位置指针进行更新。
同时,在 channel 的实现中,还会使用一个信号量来控制 Goroutine 的调度,以避免过度使用 CPU。在一个无缓存的 channel 中,每个发送和接收操作都会导致 Goroutine 在对应的锁上阻塞,直到另一个 Goroutine 进行相应的操作为止。而在有缓存的 channel 中,由于可以提前存储一部分数据,因此只有当 channel 中的缓存被填满或者为空时才会导致 Goroutine 阻塞。
通过这种方式,Golang 中的 channel 实现可以高效地实现 Goroutine 之间的通讯,从而使得并发编程变得更加简单和可控。