Go语言之并发

125 阅读4分钟

并发是go为什么被重视的重要原因,相比于其他语言来说,go语言实现并发是非常容易的一件事

启动多个goroutine

当我们启动了多个goroutine时,可能有些goroutine还没有执行完main函数就已经退出了,main函数一结束其余的goroutine就直接退出了,为了解决这一问题我们可以使用sync.WaitGroup来解决这一问题,例如:

var wg sync.WaitGroup

func count(i int) {
	defer wg.Done() // goroutine结束就-1
	fmt.Println("Goroutine:", i)
}
func main() {

	for i := 0; i < 10; i++ {
		wg.Add(1) // 启动一个goroutine就+1
		go hello(i)
	}
	wg.Wait() // 等待所有登记的goroutine都结束
}

上面启动了10个goroutine并发执行,打印的i顺序是不定的,但是可以确保所有都执行完再结束main函数

GMP模型

Go语言运行时有自己的一套调用系统,与OS线程调度是有区别的 G(goroutine):,里面有自己的信息,还有与P的一些关系的信息,它非常地轻量,一个goroutine只占几KB P(Processor):管理一组goroutine队列,如果M要调用G时,就必须在这里获取 M(thread):每个M都代表了1个内核线程,OS调度器负责把内核线程分配到CPU上运行

image.png 我们看一下每个节点的作用: CPU:线程M在CPU上运行,一个CPU一次只能处理一个线程

M:通过P的本地队列中获取G,把G放在M上执行,执行完再获取

OS调度器:管理M和CPU之间的调度

P:goroutine 执行所需的资源,协调M和G,所有P都在程序启动时创建,最多有GOMAXPROCS个。P与M一一对应,当队列消费完了就会去全局队列里取,如果全局队列也没了就会去其他P里抢任务

goroutine调度器:管理P和G之间的调度

P的本地队列:存放等待运行的G, 新建G时会优先加入到P的本地队列, 如果队列满了, 则会把本地队列中一半的G移动到全局队列

全局队列:存放等待运行的G

如果需要更详细的解析,以上的GMP模型图文参考:www.jianshu.com/p/90d20e3da…

GOMZXPROCS

GOMZXPROCS调度器的默认值决定于这台机子上有几个核心,他就有几个,我们可以通过runtime.GOMAXPROCS()来设置并发时占用几个CPU

Go语言中的操作系统线程和goroutine的关系:

  1. 一个线程对应着多个goroutine。
  2. go程序可以同时使用多个操作系统线程。
  3. goroutine和OS线程是多对多的关系。

CSP(Communicating Sequential Processes)并发模型

使用信道通信共享内存,go语言中通过channel连接起goroutine,goroutine之间通过channel通信,channel是引用类型,像一个队列,数据实行先进先出原则

锁确保并发安全

互斥锁

它可以确保同一时间只有一个goroutine访问共享的资源下面来解释一些为什么需要互斥锁,先看下面代码:

var x int64
var wg sync.WaitGroup
var lock sync.Mutex  // 互斥锁

func add() {
	for i := 0; i < 5000; i++ {
		lock.Lock() // 加锁
		x = x + 1
		lock.Unlock() // 解锁
	}
	wg.Done()
}
func main() {
	wg.Add(2)
	go add()
	go add()
	wg.Wait()
	fmt.Println(x)
}

x=x+1是分为三部执行的,第一步从一个地方取出x,第二步对x加1,第三步放回去,再这个过程中如果其他的goroutine来拿x则拿到的x可能是还没加完的x,这样就会导致数据混乱;

这个时候为了使这三步一气呵成,就可以在其执行前加互斥锁,使其他goroutine不能访问,执行完了再解锁;当互斥锁释放后,等待的goroutine才可以获取锁进入临界区,多个goroutine同时等待一个锁时,唤醒的策略是随机的。

读写互斥锁

读写锁分为两种:读锁和写锁。

加上读锁后:说明我要开始读了,其他人可以来读,但是不能写

加上写锁后:我要写了,其他人什么都不能干

读写锁使用的是sync包中的RWMutex类型,可以这样var rwlock sync.RWMutex定义读写锁,rwlock.Lock()加写锁,对应rwlock.Unlock解锁;rwlock.RLock()加写锁,对应rwlock.RUnlock解锁;