在 Go 语言中,协程(goroutine) 是一种轻量级的线程,由 Go 运行时(Go runtime)进行管理。
Goroutine 的特点
-
轻量级:与传统的操作系统线程相比,goroutine 的创建和销毁成本非常低。这得益于 Go 运行时对线程池和调度器的优化。
-
并发执行:多个 goroutine 可以在同一个操作系统线程上并发执行,由 Go 运行时调度器自动管理。
-
栈大小动态调整:goroutine 的栈大小是动态调整的,初始时很小(通常几 KB),在需要时会自动增长,从而避免了不必要的内存浪费。
既然协程goroutine是轻量级线程,那么就意味着多个协程很可能属于一个线程,那么在Golang中使用协程会不会发生分配的5个协程都在一个线程中,导致操作系统没有真正的使用多核cpu并行处理多个任务?
Go runtime 会将(同一任务的)协程(goroutine)分配给不同的操作系统线程,由 Go 的调度器(scheduler)根据当前系统的负载和配置动态分配。
Go 的调度器(scheduler)基于 GMP 模型,其中 G 代表 goroutine,M 代表操作系统线程,P 代表逻辑处理器。
每个操作系统线程(M)都可以绑定到一个逻辑处理器(P)上,逻辑处理器负责管理 goroutine 队列和调度 goroutine 的执行。
当一个新的协程被创建时,它会被放入调度器的全局队列或某个逻辑处理器的本地队列中等待执行。
调度器会根据当前系统的负载、线程的数量、逻辑处理器的状态等因素,动态地将 goroutine 分配给合适的操作系统线程执行。
协程在相同的地址空间中运行,因此对共享内存的访问必须进行同步。协程在相同的地址空间中运行,因此对共享内存的访问必须是同步阻塞的。协程在相同的地址空间中运行,因此对共享内存的访问必须是同步访问。
协程(goroutine)是轻量级线程,它们在相同的地址空间中运行,这意味着多个协程可以直接访问相同的全局变量、指针指向的堆上数据等。如果多个协程同时读写共享变量,可能会出现数据竞争(data race),导致未定义行为,因此需要同步机制(如 sync.Mutex
、sync.RWMutex
或 sync/atomic
)来保证数据一致性。
Go 官方推荐使用 channel 而非锁来避免数据竞争,典型的 Go 并发风格是:
func main() {// Channel是类型安全的,这确保了通过它传递的数据的一致性。类型安全特性减少了因类型不匹配而导致的错误。// Channel内置了同步机制,发送和接收操作会自动进行同步,从而保证了共享数据的一致性。这种内置同步机制减少了程序员需要手动编写的同步代码量。// Channel传递数据时,内存屏障会自动插入,以保证内存操作的可见性和一致性。// Channel的发送和接收操作是原子的,这意味着它们不会被中断或干扰,从而确保了数据的一致性和顺序性。ch := make(chan int, 1)go func() {ch <- 42 // 发送数据}()value := <-ch // 接收数据fmt.Println(value)
}