Go并发| 青训营

86 阅读3分钟

Go并发| 青训营

并发和并行

  • 并发是指在同一时间内,多个任务在交替执行,看起来好像是同时执行的。这是因为计算机会在不同的线程之间切换,每个线程都在执行不同的任务;
  • 并行是指在同一时间内,多个任务真正的同时执行。这种情况通常发生在多台计算机或多个处理器上,每个处理器都在执行不同的任务。

简单来说呢,并发就是应该处理器处理多个任务,而并行是指多个处理器同时处理多个任务。

下面是并发和并行的对比展示:

image.png

并发其实还是相当于串行,CPU在一个时间片里只处理一个任务,只不过这个切换对于用户来说太快以致于没有察觉;而并行才是真正意义上的同时进行多个任务。

进程、线程、协程

进程是程序执行的过程,包括了动态创建、调度和消亡的整个过程,进程是程序资源管理的最小单位

线程是操作操作系统能够进行运算调度的最小单位。线程被包含在进程之中,是进程中的实际运作单位,一个进程内可以包含多个线程,线程是资源调度的最小单位。

线程在创建时需要消耗一定的系统资源,线程属于内核态,线程的创建、切换、停止都属于比较重量级的系统操作,占用系统栈资源属于MB级别。

协程是属于用户态的,属于轻量级线程,协程的创建、切换、销毁都是由Go语言本身完成,比线程消耗的资源少的多,占用系统栈资源属于KB级别。

一个线程里可以同时执行多个协程Go可以同时创建上万级别的协程,也是Go支持高并发原因之一。

Go创建协程十分简单,只需要在调用的函数前加一个go关键字即可:

func hello() {
    for i := 0; i < 100; i++ {
        fmt.Println(i)
    }
}
​
func main() {
    go hello()
​
    for i := 0; i < 100; i++ {
        fmt.Println(i)
    }
}
​

CSP

CSP(Communicating Sequential Process)模型是上个世纪七十年代提出的,用于描述两个独立的并发实体通过共享的通讯 channel(管道)进行通信的并发模型。 CSP中channel是第一类对象,它不关注发送消息的实体,而关注与发送消息时使用的channel。

Golang 就是借用CSP模型的一些概念为之实现并发进行理论支持,其实从实际上出发,go语言并没有,完全实现了CSP模型的所有理论,仅仅是借用了 process和channel这两个概念。process是在go语言上的表现就是 goroutine 是实际并发执行的实体,每个实体之间是通过channel通讯来实现数据共享。

Golang中推崇不要通过共享内存来通信,而应该通过通信来共享内存

image.png

共享内存会涉及到多个线程同时访问修改数据的情况,那得保证数据的安全性,可见性,那就会加锁,加锁会让并行变为串行,cpu也忙于线程抢锁。 不如换一种方式,把数据复制一份,每个线程有自己的,只要一个线程干完一件事其他线程不用去抢锁了,这就是一种通信方式,把共享的以通知方式交给线程,实现并发。

通道

Golang中的通道叫做Channel,每个通道只允许交换指定类型的数据,使用chan关键字来声明一个通道,使用 close函数来关闭通道。

ch := make(chan 数据类型,[缓冲大小])
close(ch)

在声明的时候是否设置了缓冲大小代表了该通道是否具有缓冲功能。

image.png

而通过操作符<- ( -> )来指定通道的方向,实现发送或接收。

ch <- 10 // 发送 
x := <- ch // 接收

Lock

Go当然也支持处理共享内存时通过锁来防止数据的竞争问题,提供了两种线程安全的锁机制:

  1. sync.Mutex

    互斥锁,确保同时只有一个goroutine可以访问共享数据。

    var mu sync.Mutex
    mu.Lock()
    // 访问共享资源
    mu.Unlock() 
    
  2. sync.RWMutex

读写互斥锁,可以同时允许多个读,但写时独占。读写互斥锁实现了读写分离,相比普通的互斥锁可以提供更高的并发性能。

var mu sync.RWMutex
​
mu.RLock()
// 读共享资源
mu.RUnlock()
​
mu.Lock()
// 写共享资源
mu.Unlock()

WaitGroup

sync.WaitGroup是Go语言中的一个常用同步工具,可以用于等待一组goroutine结束。

WaitGroup3个方法,AddDoneWait

使用方法:当开启协程时调用Add方法增加一个计数,完成时调用Done方法减去一个计数,Wait会一直阻塞直到WaitGroup的值为 0

func main() {
    var wg sync.WaitGroup
    // 开启5个计数
    wg.Add(5)
    for i := 0; i < 5; i++ {
        go func(j int) {
            // 执行完时调用Donw
            defer wg.Done()
                hello(j)
            }(i)
    }
    // 等待所有协程执行完
    wg.Wait()
}
​