Goland学习笔记 sync.WaitGroup
go语言是原生支持并发的语言,在学习并发之前,我们先来学习一下进程、线程以及协程的概念。下面的内容取至《计算机操作系统基础》
前置知识
进程
我们都知道计算机的核心是CPU,它承担了所有的计算任务;而操作系统是计算机的管理者,它负责任务的调度、资源的分配和管理,统领整个计算机硬件;应用程序则是具有某种功能的程序,程序是运行于操作系统之上的。
进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。
进程一般由程序、数据集合和进程控制块三部分组成。
程序用于描述进程要完成的功能,是控制进程执行的指令集;
数据集合是程序在执行时所需要的数据和工作区;
程序控制块(Program Control Block,简称PCB),包含进程的描述信息和控制信息,是进程存在的唯一标志。
进程具有的特征:
动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
并发性:任何进程都可以同其他进程一起并发执行;
独立性:进程是系统进行资源分配和调度的一个独立单位;
结构性:进程由程序、数据和进程控制块三部分组成。
线程
在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。
后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程。
线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID、当前指令指针(PC)、寄存器和堆栈组成。而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。
协程
协程,英文Coroutines,是一种基于线程之上,但又比线程更加轻量级的存在,这种由程序员自己写程序来管理的轻量级线程叫做『用户空间线程』,具有对内核来说不可见的特性。
因为是自主开辟的异步任务,所以很多人也更喜欢叫它们纤程(Fiber),或者绿色线程(GreenThread)。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。
sync.waitGroup
介绍
waitGroup有一个内置的计数器,最初从0开始。它具有三个方法Add(),Done(),Wait()。Add()计数器加1,Done()计数器减1,Wait()阻塞代码运行,直到计数器为0.
直白的说就是一堆小弟在干活,干完活就跑到一个通道里面发送一个空消息,老大在通道等着小弟们的汇报。来一个小弟就记录一个小弟干完了活,直到记录的小弟干完活的数量等于小弟的人数就完成了。
下面是不使用sync.waitGroup的代码实现:
works := 3
ch := make(chan struct{})
work := func(){
// 搬砖
ch <- struct{} // 通知老大
}
leader := func() {
cnt := 0
for range ch {
cnt++
if cnt == workers {
break
}
}
close(ch)
// 检查工作成果
}
go leader()
for i := 0; i < workers; i++ {
go worker()
}
那么如果采用sync.waitGroup则
wg := &sync.WaitGroup{}
workers := 3
wg.Add(workers)
worker := func() {
defer wg.Done()
// 干活
}
leader := func() {
wg.Wait()
// 检查工作成果
}
go leader()
for i := 0; i < workers; i++ {
go worker()
}