go语言进阶部分-并发 | 青训营笔记

27 阅读2分钟

并发VS并行

并发是指多个线程运行在一个核的CPU上运行

并行是指多个线程运行在多个核的CPU上

Goroutine

协程:用户态,轻量级线程,栈KB级别

线程:内核态,线程跑多个协程,栈MB级别

使用方式: 快速打印hello goroutine

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

func main() {
	for i := 0; i < 5; i++ {
        //使用go关键字开启
		go func(j int) {
			hello(j)
		}(i)
	}
	time.Sleep(time.Second)
}

CSP

提倡通过通信共享内存而不是通过共享内存而实现通信

Channel

make(chan 元素类型,[缓冲大小])

  • 无缓冲通道 make(chan int) 同步通道
  • 有缓冲通道 make(chan int,2) 异步通道

一个案例:

A子协程发送0~9数字

B子协程计算输入数字的平方

主协程输出最后的平方数

package main

func main() {
	src := make(chan int)
	dest := make(chan int, 3)

	//A子协程
	go func() {
		defer close(src)
		for i := 0; i < 10; i++ {
			src <- i
		}
	}()

	//B子协程
	go func() {
		defer close(dest)
		for i := 0; i < 10; i++ {
			dest <- i * i
		}
	}()

	//主协程
	for i := range dest {
		println(i)
	}
}

并发安全Lock

go中的lock是sync.Mutex类型

package main

import (
	"fmt"
	"sync"
	"time"
)

var (
	x    int64
	lock sync.Mutex
)

// 使用锁进行累加
func addWithLock() {
	for i := 0; i < 2000; i++ {
		lock.Lock()
		x += 1
		lock.Unlock()
	}
}

func addWithoutLock() {
	for i := 0; i < 2000; i++ {
		x += 1
	}
}

func main() {
	x = 0
	for i := 0; i < 5; i++ {
		go addWithoutLock()
	}

	time.Sleep(time.Second)
	fmt.Println("不使用锁:", x)
	x = 0
	for i := 0; i < 5; i++ {
		go addWithLock()
	}
	time.Sleep(time.Second)
	fmt.Println("使用锁:", x)
}

最后的输出结果

不使用锁: 7363
使用锁: 10000

可以看到多个协程是会出现并发安全的问题的

WaitGroup

之前阻塞协程一直使用的是Sleep,但是一个协程执行的实现可能小于设置的时间。Go语言提供了一个WaitGroup来精确的获取一个协程的执行时间

其实就是一个计数器,开启协程+1,执行结束-1,主协程阻塞直到计数器为0

对前面的demo进行优化

package main

import (
	"fmt"
	"sync"
)

var (
	x    int64
	lock sync.Mutex
	wg   sync.WaitGroup
)

// 使用锁进行累加
func addWithLock() {
	for i := 0; i < 2000; i++ {
		lock.Lock()
		x += 1
		lock.Unlock()
	}
	wg.Done()
}

func addWithoutLock() {
	for i := 0; i < 2000; i++ {
		x += 1
	}
	wg.Done()
}

func main() {
	x = 0
	wg.Add(5)
	for i := 0; i < 5; i++ {
		go addWithoutLock()
	}
	wg.Wait()
	fmt.Println("不使用锁:", x)
	x = 0
	wg.Add(5)
	for i := 0; i < 5; i++ {
		go addWithLock()
	}
	wg.Wait()
	fmt.Println("使用锁:", x)
}