GO语言并发编程入门:Goroutine、Channel、Context、并发安全、GMP调度模型

2023年 8月 13日 43.3k 0

GO语言并发编程入门:Goroutine、Channel、Context、并发安全、GMP调度模型

1.GO并发介绍

并发:多线程程序在一个核的cpu上运行。
并行:多线程程序在多个核的cpu上运行。
由上可知并发不是并行,并行是直接利用多核实现多线程的运行,并发则主要由切换时间片来实现”同时”运行,go可以设置使用核数,以发挥多核计算机的能力。

Go语言之所以被称为现代化的编程语言,就是因为它在语言层面已经内置了调度和上下文切换的机制。Go语言的并发编程特点主要体现在Goroutine协程和Channel通道的使用上。

  • Goroutine协程:Goroutine是Go语言中的并发执行单位。它是一种轻量级的协程,由负责整个Go程序的执行的底层系统组件Go运行时(Go runtime)调度和管理。在调用函数的时候在前面加上go关键字,就可以为一个函数创建一个goroutine。与传统的线程、协程相比,Goroutine的创建和销毁代价非常小,可以高效地创建大量的Goroutine,Goroutine 奉行通过通信来共享内存,而不是共享内存来通信。Goroutine4~5KB的栈内存占用和由于实现机制而大幅减少的创建和销毁开销是go高并发的根本原因。
  • Channel通道:通道是Goroutine之间进行安全通信和数据共享的机制。它提供了同步和互斥的功能,确保数据的有序传输和访问。通过通道,不同的Goroutine可以安全地进行数据交换和共享状态。

Goroutine是一种特殊的协程,这是因为普通的协程和操作系统线程是多对一的关系,而在Go语言中,Goroutine和操作系统线程是多对多的关系。具体来说:

  • 一个操作系统线程(OS Thread)可以对应多个Goroutine。
  • Go程序可以同时使用多个操作系统线程,这使得Go语言能够充分利用多核处理器的计算能力。
  • Go运行时调度器(Go Scheduler)负责将多个Goroutine调度到少量的操作系统线程上执行,并处理它们之间的通信。

2.GO并发编程

2.1 父子协程

在Go语言中,可以通过创建协程(Goroutine)来实现并发执行的任务。当父协程创建一个子协程时,父协程和子协程是相互独立的并发执行单元。父协程可以继续执行其他操作,而不需要等待子协程完成。子协程会在创建后立即开始执行,与父协程并发执行。父协程和子协程之间不存在直接的调用关系,它们是相互独立的执行流程。父协程的结束不会影响子协程的执行。即使父协程结束,子协程仍然可以继续执行,直到完成或被终止。父协程和子协程之间是独立的执行上下文,彼此之间的运行状态不会相互影响。
然而,需要注意的是,如果主协程(即main函数所在的协程)结束了,整个程序会终止,所有的协程也会被强制结束。这意味着如果主协程提前结束,尚未完成的子协程也会被中止。因此,在使用协程进行并发编程时,我们需要确保主协程不会过早地结束,以确保子协程能够完成任务,可以考虑采用以下方法:

  • 使用time.Sleep使协程睡眠确保并发子协程完成

  • 使用sync.WaitGroup等待组确保并发子协程完成

  • 2.1.1 使用time.Sleep使协程睡眠确保并发子协程完成

    time包提供了时间相关的功能,其中最常用的是time.Sleep函数,它可以让当前的Goroutine休眠一段时间。通过结合Goroutine和time.Sleep,我们可以实现协程的并发执行。

    package main
    import (
    	"fmt"
    	"time"
    )
    func main() {
    	go task("Task 1")  // 启动协程1
    	go task("Task 2")  // 启动协程2
    	// 主协程休眠一段时间,确保协程有足够的时间执行
    	time.Sleep(3 * time.Second)
    }
    func task(name string) {
    	for i := 0; i < 5; i++ {
    		fmt.Println(name+":", i) // 打印任务名称和当前迭代值
    		time.Sleep(500 * time.Millisecond)
    	}
    }
    

    在上面的示例中,我们通过启动两个协程(task1和task2)来实现并发执行。主协程(main函数)休眠3秒钟,确保协程有足够的时间执行。这样,我们就实现了协程的并发执行。

    2.1.2 使用sync.WaitGroup等待组确保并发子协程完成

    sync.WaitGroup文档介绍:draveness.me/golang/docs…
    sync包提供了一些同步原语,如WaitGroup等待组,它可以用来等待一组协程的完成。通过WaitGroup,类似于操作系统中的PV信号量,可以实现协程的并发执行和同步等待。

    package main
    import (
    	"fmt"
    	"sync"
    )
    func main() {
    	var wg sync.WaitGroup
    	wg.Add(2) // 设置等待组的计数器为2,表示有两个协程需要等待
    	go func() {//开启协程1
    		defer wg.Done() // 协程完成后调用Done方法,减少等待组的计数器
    		task("Task 1") 
    	}()
    	go func() {//开启协程1
    		defer wg.Done() // 协程完成后调用Done方法,减少等待组的计数器
    		task("Task 2") 
    	}()
    	wg.Wait() // 等待所有协程完成,完成后才结束main协程
    }
    func task(name string) {
    	for i := 0; i < 5; i++ {
    		fmt.Println(name+":", i) // 打印任务名称和当前迭代值
    	}
    }
    
    

    在上述示例中,我们使用sync包中的WaitGroup来实现协程的并发执行和同步等待。通过调用wg.Add方法设置等待组的计数器为2,然后在每个协程中使用defer wg.Done()来减少计数器。最后,通过wg.Wait()等待所有协程完成。

    2.2 Channel实现并发与协程通信

    Channel文档介绍:draveness.me/golang/docs…
    在并发编程中,单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义,对共享资源的正确访问需要精确的控制。
    在目前的绝大多数语言中,都是通过加锁等线程同步方案来解决这一困难问题,而Go语言却另辟蹊径,它将共享的值通过Channel传递(实际上多个独立执行的线程很少主动共享资源)。channel像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序,可以让一个goroutine发送特定值到另一个goroutine的通信机制。在任意给定的时刻,最好只有一个Goroutine能够拥有该资源,数据竞争从设计层面上就被杜绝了。
    这也是Go语言作者提出的并发编程哲学:不要通过共享内存来通信,而应通过通信来共享内存。

    虽然我们在 Go 语言中也能使用共享内存加互斥锁进行通信,但是 Go 语言提供了一种不同的并发模型,即通信顺序进程(Communicating sequential processes,CSP)。Goroutine 和 Channel 分别对应 CSP 中的实体和传递信息的媒介,Goroutine 之间会通过 Channel 传递数据。

    CSP(Communicating sequential processes)是一种并发编程模型,它强调通过通信来实现并发。在CSP中,程序被分解成一组独立的进程,这些进程通过通道进行通信。通道是一种同步的通机制,它允许进程之间传递数据。CSP模型的一个重要特点是,进程之间的通信是通过发送和收消息来实现的,而不是通过共享内存。
    CSP模型的一个优点是,它可以避免一些常见的并发编程问题,例如死锁和竞态条件。这是因为CSP模型中的进程是独立的,们不会相互干扰或阻塞彼此。此外,CSP模型还可以使并发程序更易于理解和调试,因为它们的行为是通过进程之间的通信来定义的。

    在这里插入图片描述
    上图中的两个 Goroutine,一个会向 Channel 中发送数据,另一个会从 Channel 中接收数据,它们两者能够独立运行并不存在直接关联,但是能通过 Channel 间接完成通信。

    2.2.1 使用Channel实现协程并发并进行协程通信

    package main
    import "fmt"
    func main() {
    ch := make(chan int) // 创建一个int类型的通道

    go func() {
    ch

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论