GO语言工程实践之并发编程 | 青训营笔记

68 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天

一、并发编程

简而言之,所谓并发编程是指在一台处理器上“同时”处理多个任务。

随着硬件的发展,并发程序变得越来越重要。Web服务器会一次处理成千上万的请求。平板电脑和手机app在渲染用户画面同时还会在后台执行各种计算任务和网络请求。即使是传统的批处理问题--读取数据,计算,写输出--现在也会用并发来隐藏掉I/O的操作延迟以充分利用现代计算机设备的多个核心。计算机的性能每年都在以非线性的速度增长。

宏观的并发是指在一段时间内,有多个程序在同时运行。

并发在微观上,是指在同一时刻只能有一条指令执行,但多个程序指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个程序快速交替的执行。

并行和并发

并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。

1673841463044.png 并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,通过cpu时间片轮转使多个进程快速交替的执行。

1673841524632.png

1.Goroutine(协程)

goroutine 是 Go语言中的轻量级线程实现,由 Go 运行时(runtime)管理。Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU。

Go 程序从 main 包的 main() 函数开始,在程序启动时,Go 程序就会为 main() 函数创建一个默认的 goroutine。Go 程序中使用 go 关键字为一个函数创建一个 goroutine。一个函数可以被创建多个 goroutine,一个 goroutine 必定对应一个函数

package main  

import (  
        "fmt"  
        "time"  
)  

func say(s string) {  
            for i := 0; i < 5; i++ {  
                time.Sleep(100 * time.Millisecond)  
                fmt.Println(s)  
            }  
            }  

func main() {  
        go say("world")  
        say("hello")  
}

2.CSP(Communication Sequential Process)

CSP——通讯顺序进程,是著名计算机科学家C.A.R.Hoare为解决并发现象而提出的代数理论,是一个专为描述并发系统中通过消息交换进行交互通信实体行为而设计的一种抽象语言。

1673843076788.png

3.Channel(通道)

如果说 goroutine 是 Go语言程序的并发体的话,那么 channels 就是它们之间的通信机制。一个 channels 是一个通信机制,它可以让一个 goroutine 通过它给另一个 goroutine 发送值信息。每个 channel 都有一个特殊的类型,也就是 channels 可发送数据的类型。一个可以发送 int 类型数据的 channel 一般写为 chan int。

Go语言提倡使用通信的方法代替共享内存,当一个资源需要在 goroutine 之间共享时,通道在 goroutine 之间架起了一个管道,并提供了确保同步交换数据的机制。声明通道时,需要指定将要被共享的数据的类型。可以通过通道共享内置类型、命名类型、结构类型和引用类型的值或者指针。

这里通信的方法就是使用通道(channel),如下图所示。

1673843307500.png

实例
package main  

import "fmt"  

func sum(s []int, c chan int) {  
        sum := 0  
        for _, v := range s {  
                sum += v  
        }  
        c <- sum // 把 sum 发送到通道 c  
}  

func main() {  
        s := []int{728-940}  

        c := make(chan int)  
        go sum(s[:len(s)/2], c)  
        go sum(s[len(s)/2:], c)  
        x, y := <-c, <-c // 从通道 c 中接收  

        fmt.Println(x, y, x+y)  
}

4.并发安全lock

Go语言包中的 sync 包提供了两种锁类型:sync.Mutex 和 sync.RWMutex。Mutex 是最简单的一种锁类型,同时也比较暴力,当一个 goroutine 获得了 Mutex 后,其他 goroutine 就只能乖乖等到这个 goroutine 释放该 Mutex。RWMutex 相对友好些,是经典的单写多读模型。在读锁占用的情况下,会阻止写,但不阻止读,也就是多个 goroutine 可同时获取读锁(调用 RLock() 方法;而写锁(调用 Lock() 方法)会阻止任何其他 goroutine(无论读和写)进来,整个锁相当于由该 goroutine 独占。从 RWMutex 的实现看,RWMutex 类型其实组合了 Mutex:

5.Waitgroup

WaitGroup 一共有三个方法:

(wg *WaitGroup) Add(delta int)
(wg *WaitGroup) Done()
(wg *WaitGroup) Wait()
  • Add 方法用于设置 WaitGroup 的计数值,可以理解为子任务的数量
  • Done 方法用于将 WaitGroup 的计数值减一,可以理解为完成一个子任务
  • Wait 方法用于阻塞调用者,直到 WaitGroup 的计数值为0,即所有子任务都完成