初探Go语言并发编程 | 青训营笔记

84 阅读2分钟

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

Go语言并发编程

并发:多线程程序通过时间片的切换在一个核的CPU上运行

并行:多线程程序直接利用多核CPU实现同时运行

1 Goroutine

协程:用户态,轻量级线程,栈KB级别,可以由Go语言本身完成,比线程轻量

线程:内核态,线程跑多个协程,栈MB级别,切换、创建、停止都属于比较昂贵的系统操作

Go语言中使用go关键字创建协程:

func hello(i int) {
    fmt.Println("hello, goroutine: " + fmt.Sprint(i))
}

func HelloGoRoutine() {
    for i := 0; i < 5; i++ {
        // 使用`go`关键字创建协程
        go func(j int) {
            hello(j)
        }(i)
    }
    // 使用Sleep阻塞,保证子协程完成前主线程不退出
    time.Sleep(time.Second)
}

2 CSP (Communicating Sequential Processes)

通过通信共享内存:需要一个channel (通道),它遵循先入先出的原则,以保证收发数据的顺序;

通过共享内存通信:通过互斥量加锁获取临界区的权限,存在竞态条件和数据竞争的问题,影响程序的性能;

通过通信共享内存(Channel)

Go语言中使用make方法创建channel,make(chan <元素类型>, <缓冲大小>)

make(chan int)       // 无缓冲通道
make(chan int, 2)    // 能存放2个元素的有缓冲通道

无缓冲通道也被称为同步通道

使用channel实现生产-消费模式的示例:

func CalSquare() {
    src := make(chan int)
    dst := make(chan int, 3)
    // 子协程A 发送0~9的数字
    go func() {
        defer close(src)
        for i := 0; i < 10; i++ {
                src <- i
        }
    }()
    // 子协程B 获得协程A发送的数字 然后进行平方操作
    go func() {
        defer close(dst)
        for i := range src {
                dst <- i * i
        }
    }()
    // 主协程 获得B协程计算的结果 然后打印输出
    for i := range dst {
        fmt.Println(i)
    }
}
  • 使用<-channel发送消息,使用for range获取消息;
  • 有创建就有关闭,使用defer close(chennel)关闭通道;
  • 根据生产者的生产速度和消费者的消费速度,灵活的使用有缓冲和无缓冲通道,可以有效解决生产消费的不均衡的问题。

通过共享内存通信(Lock)

var (
    x    int64      // 共享内存
    lock sync.Mutex // 互斥锁
)

func addWithLock() {
    for i := 0; i < 5000; i++ {
        // 临界区保护
        lock.Lock() // 加锁
        x += 1
        lock.Unlock() // 解锁
    }
}

func addWithoutLock() {
    for i := 0; i < 5000; i++ {
        // 临界区无保护
        x += 1
    }
}
  • 不对临界区进行保护,输出结果未知;
  • 使用互斥锁来保证并发安全;

3 WaitGroup

const goRoutineNum = 5

func hello(i int) {
    fmt.Println("hello, goroutine: " + fmt.Sprint(i))
}

func HelloGoRoutine() {
    var wg sync.WaitGroup
    wg.Add(goRoutineNum) // 添加协程数
    for i := 0; i < goRoutineNum; i++ {
        go func(j int) {
            defer wg.Done() // 表示一个协程完成
            hello(j)
        }(i)
    }
    wg.Wait() // 阻塞
}
  • Add添加协程数;
  • Done表示一个协程的任务完成;
  • Wait阻塞,保证子协程完成前主线程不退出;