并发编程| 青训营笔记

86 阅读5分钟

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

并发和并行的区别

在 Go 语言中,"并发"和"并行"是两个不同的概念,尽管它们经常一起使用。它们的区别如下:

  1. 并发:在 Go 语言中,并发是指多个任务在一个处理器上交替执行。Go 语言提供了 goroutine 来实现并发。goroutine 是一种轻量级线程,可以在同一进程内同时运行多个任务。通过使用 goroutine,Go 程序可以在单个处理器上同时执行多个任务,以此来实现并发执行。
  2. 并行:在 Go 语言中,并行是指多个任务在多个处理器上同时执行。与并发不同,要实现并行执行,必须在多个处理器上同时运行多个 goroutine。在 Go 语言中,可以使用 GOMAXPROCS 环境变量来设置可以并行执行的 goroutine 的数量。通过并行执行,Go 程序可以显著提高性能和吞吐量。

综上所述,Go 语言中的并发和并行是两个不同的概念。并发指的是在单个处理器上同时执行多个任务,而并行指的是在多个处理器上同时执行多个任务。Go 语言通过 goroutine 和 GOMAXPROCS 环境变量提供了方便的方法来实现并发和并行。

协程Goroutine

当我们使用 Go 语言进行并发编程时,Goroutine 是非常重要的一个概念。Goroutine 是一种轻量级线程,它由 Go 运行时系统管理,而不是由操作系统管理。相比于操作系统线程,Goroutine 的创建和销毁成本非常低,因此可以创建大量的 Goroutine 来实现高并发的应用。

下面是 Goroutine 的一些特点和使用方法:

  1. 轻量级:Goroutine 比线程更轻量级,创建和销毁 Goroutine 的开销非常小。因此,Goroutine 可以创建成千上万个,而线程则不能。
  2. 并发:Goroutine 可以实现并发执行,多个 Goroutine 可以同时执行,而不需要显式的线程同步。
  3. 简单易用:使用 Goroutine 编写代码非常简单,只需要在函数或方法调用前添加 go 关键字即可创建 Goroutine。Goroutine 在编写代码时提供了一种更加直观的并发模型。
  4. 可以与信道 Channel 一起使用:信道是用于 Goroutine 之间通信和同步的重要机制。Goroutine 可以通过信道实现同步、共享数据等操作。
  5. 可以使用多核 CPU:Go 运行时系统可以自动将多个 Goroutine 分配到多个 CPU 核心上运行,从而实现并行运行。

下面是一个简单的示例,展示了如何使用 Goroutine 实现并发:

package main

import (
    "fmt"
    "time"
)

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

func main() {
    go printString("hello") // 创建 Goroutine
    go printString("world") // 创建 Goroutine

    time.Sleep(1 * time.Second) // 等待 Goroutine 完成
}

在上面的示例中,我们定义了一个函数 printString,该函数会打印出传入的字符串,并在每次打印之间等待 100 毫秒。然后,在 main 函数中创建了两个 Goroutine 来并发地执行 printString 函数,最后使用 time.Sleep 函数等待 Goroutine 执行完毕。这样,我们就可以实现两个字符串并发地输出。

并发模型CSP(communicating sequential processes)

CSP(Communicating Sequential Processes)是一种并发计算模型,它最初由 Tony Hoare 在 1978 年提出,旨在通过消息传递来实现并发计算。

CSP 模型描述了多个独立的并发进程,这些进程可以进行通信,但彼此之间是隔离的。每个进程都是按照自己的节奏和规则执行的,并且不知道其他进程的状态。进程之间通过通信传递消息来交互,进程可以发送消息,也可以接收消息。这种通信方式可以很好地避免并发编程中的一些问题,例如死锁和竞争条件等。

在 CSP 模型中,每个进程都是原子的、独立的计算单元。每个进程都可以进行 I/O 操作,例如从通信通道读取或写入数据。进程之间的通信是基于通信通道(channel)的,通道是 CSP 模型中非常重要的一个概念。通道可以看作是一种特殊的队列,进程可以向通道中发送消息,也可以从通道中接收消息。通道在 CSP 模型中起到了非常重要的作用,它们可以保证进程之间的通信是线程安全的,并且可以避免一些常见的并发编程问题。

并发安全锁Lock

在 Go 中,有两种并发安全的锁:互斥锁(mutex)和读写锁(RWMutex)。

  1. 互斥锁(Mutex):互斥锁是最基本的锁类型,在 Go 中通过 sync 包来实现。一个互斥锁有两个状态:锁定和未锁定。只有一个 goroutine 可以获得这个锁,如果其他 goroutine 试图获取这个锁,它们就会被阻塞,直到持有锁的 goroutine 释放锁。
  2. 读写锁(RWMutex):读写锁是一种更高级别的锁,它支持同时读取共享资源的多个 goroutine,但只支持一个 goroutine 写入共享资源。在 Go 中,读写锁也是通过 sync 包来实现的。读写锁有三种状态:读锁定、写锁定和未锁定。读锁定可以由多个 goroutine 同时持有,写锁定只能被一个 goroutine 持有,如果有任何 goroutine 持有写锁定,任何其他 goroutine 都不能获取读或写锁定。

使用锁可以保证多个 goroutine 在并发执行时,对共享资源的访问是同步和安全的。但是,过度使用锁会降低并发性能。因此,在编写并发代码时,需要根据实际情况选择适当的锁类型,并尽可能减少锁的使用次数,以提高程序的并发性能。