Go的基础语法-协程 | 青训营

55 阅读4分钟

什么是协程

协程是Go语言中用于并发编程的核心概念之一。它是一种轻量级的执行单元,比传统的线程更加高效。Go中的协程称为“Goroutine”,每个Goroutine都在独立的堆栈上运行,并由Go运行时系统进行管理。

协程和进程的区别

一、 轻量级和重量级

  • 协程是一种轻量级的执行单位,由语言运行时管理,占用的资源相对较少。在Go语言中,可以创建成千上万个Goroutine,而不会造成大量的系统开销。
  • 进程是操作系统中的一个独立执行单位,它包括了独立的内存空间、代码、数据和系统资源。创建进程需要较大的系统开销,每个进程都需要拥有独立的内存空间。

二、内存共享和隔离

  • 协程通常在同一个进程内共享内存空间,它们可以轻松地共享数据和状态。这使得协程之间的通信和数据传递更加高效。
  • 进程之间的资源共享相对复杂,通常需要使用进程间通信(IPC)机制,如管道、消息队列、共享内存等,来实现数据传递和同步。

三、并发性和并行性:

  • 协程在同一线程内并发执行,多个协程可以在一个CPU核心上交替执行,通过调度器来实现并发性。这在I/O密集型任务上非常有效。
  • 进程在不同的进程中并行执行,每个进程可以在不同的CPU核心上运行。这在CPU密集型任务上比较有优势。

协程和线程的区别

一、轻量级和重量级

  • 协程是一种轻量级的执行单位,由语言运行时系统进行管理。创建和销毁协程的开销很小,可以在同一个线程内创建成千上万个协程。
  • 线程是操作系统管理的基本执行单位,通常有较大的开销。创建和销毁线程的开销相对较大,每个线程需要一定的系统资源和内存。 二、内存共享和隔离
  • 协程通常在同一个进程内共享内存空间,因此可以轻松地共享数据和状态。协程之间的通信和数据传递相对高效。
  • 线程可以在同一个进程内共享内存空间,也可以通过特定机制在不同进程间共享数据。线程间的通信需要更多的同步机制,以确保数据的一致性和正确性。 三、并发性和并行性
  • 协程在同一线程内并发执行,多个协程可以在一个CPU核心上交替运行。协程的并发性在I/O密集型任务上非常有效,但在CPU密集型任务上受限于单个CPU核心。
  • 线程在不同的CPU核心上并行执行,每个线程可以在不同的CPU核心上独立运行。线程在CPU密集型任务上有更好的并行性,可以利用多核处理器的优势。

创建一个协程

package main

import (
	"fmt"
	"time"
)

func printNumbers() {
	for i := 1; i <= 5; i++ {
		fmt.Println("Number:", i)
		time.Sleep(time.Millisecond * 500)
	}
}

func main() {
	go printNumbers() // 启动一个新的Goroutine
	fmt.Println("Main function")
	time.Sleep(time.Second * 3) // 等待一段时间,确保Goroutine有足够时间运行
}

其中使用go关键字在函数前启动一个新的Goroutine。在上述示例中,printNumbers函数在新的Goroutine中运行,与主Goroutine同时执行。

并发通信 channel

package main

import (
	"fmt"
	"time"
)

func sendData(channel chan<- int) {
	for i := 1; i <= 5; i++ {
		fmt.Println("Sending:", i)
		channel <- i // 将数据发送到通道
		time.Sleep(time.Millisecond * 500)
	}
	close(channel) // 关闭通道
}

func main() {
	dataChannel := make(chan int) // 创建一个整数类型通道
	go sendData(dataChannel) // 启动发送数据的Goroutine

	// 从通道接收数据
	for value := range dataChannel {
		fmt.Println("Received:", value)
	}
}

在上述代码中使用make(chan T)创建通道,其中T为通道中传输的数据类型。<-操作符用于将数据发送到通道或从通道接收数据。close(channel)用于关闭通道,告知接收者不再有数据。

并发选择:select语句

package main

import (
	"fmt"
	"time"
)

func main() {
	channel1 := make(chan string)
	channel2 := make(chan string)

	go func() {
		time.Sleep(time.Second)
		channel1 <- "Channel 1 message"
	}()

	go func() {
		time.Sleep(2 * time.Second)
		channel2 <- "Channel 2 message"
	}()

	select {
	case msg1 := <-channel1:
		fmt.Println("Received from Channel 1:", msg1)
	case msg2 := <-channel2:
		fmt.Println("Received from Channel 2:", msg2)
	}
}

在上述代码中select语句用于同时等待多个通道的数据到达。当多个通道都准备好时,select会随机选择一个执行。select会等待两个Goroutine的数据,哪个先准备好就会执行对应的分支。

Go语言的协程是其并发模型的基石,通过Goroutine和通道,可以轻松地实现高效的并发编程。它使得处理并行任务变得更加简单,同时还能有效利用多核处理器。不过,在使用协程时要注意数据的同步和通信,以确保并发操作的正确性和稳定性。