前言
- 本篇笔记基于《Go语言实战》
- 本篇笔记主要记录一些gopl里没提到的,或者不同说法的地方
- 这一部分都是关于并发的,比较重要,花了比较多功夫
并发
- 并行是让不同的代码片段同时在不同的物理处理器(CPU)上执行
- 并行是同时做很多事情
- 并发是同时管理很多事情,这些事情可能只做了一半就被暂停去做别的事
线程(thread)与进程(process)
- 当运行一个应用程序,操作系统会为这个程序启动一个进程,这个进程包含了应用程序在运行中需要用到和维护的各种资源(包括内存地址空间、文件和设备的句柄、线程等),相当于容器
- 一个线程是一个执行空间,这个空间会被操作系统调度(这是操作系统的线程调度器,注意区分goroutine调度器)来运行函数中所写的代码
- 每个进程至少包含一个线程,每个进程的初始线程被称为主线程
- 由于主线程的空间是应用程序本身的空间,所以主线程终止时,应用程序也会终止
- 操作系统的线程调度器可将线程调度到某个处理器(CPU)运行
- 操作系统的物理处理器(CPU)调度线程并运行该线程
goroutine概述
- Go语言的并发同步模型来自一个叫做CSP(Communicating Sequential Processes)的范型(paradigm)
- Go语言里的并发指的是能让某个函数独立于其它函数运行的能力
- 当一个函数创建为goroutine时,Go会将其视为一个独立的工作单元
- 这个单元会被调度(由goroutine调度器实现)到可用的逻辑处理器上执行
- goroutine调度器可以管理被创建的所有goroutine并为其分配执行时间;可以将操作系统的线程与语言运行时的逻辑处理器绑定,并在逻辑处理器上运行goroutine
goroutine的并发与并行
- 操作系统会在物理处理器上调度线程来运行,而Go语言程序运行时在逻辑处理器上调度goroutine运行
- 每个逻辑处理器都分别绑定到单个操作系统线程
- 在1.5版本上,Go语言运行时会为每个可用的物理处理器(CPU)分配一个逻辑处理器;在1.5版本之前,默认整个应用程序只分配一个逻辑处理器
- 如果创建一个goroutine并准备运行,这个goroutine会被放到Go调度器的全局运行队列中,之后Go调度器就将这个goroutine分配给一个逻辑处理器,这个逻辑处理器将其放入对应的本地运行队列
- 本地运行队列的goroutine会一直等待知道被分配的逻辑处理器执行
概念小整理
- 上述有关并发的概念都加粗处理了
- 物理处理器,线程,进程,主线程,应用程序,操作系统线程调度器,Go调度器,逻辑处理器,全局运行队列,本地运行队列
- 笔者的小结:
1.当启动一个应用程序,操作系统会为其创建相应的进程,
2.进程拥有应用程序所需的各种资源,包括内存地址空间,线程等
3.每个进程至少有一个线程,每个进程的初始线程被称为主线程
4.线程是一个执行空间,而主线程的空间就是应用程序的空间,因此主线程终止,应用程序也会终止
5.操作系统线程调度器可以将线程调度到物理处理器(CPU)
6.物理处理器(CPU)调度线程并运行
7.对于Go语言1.5以上版本的程序(进程)而言,Go调度器默认会为每个可用物理处理器(CPU),分配一个逻辑处理器
8.创建一个goroutine并运行,这个goroutine会被放在Go调度器的全局运行队列
9.Go调度器负责把这些队列中goroutine分配给一个逻辑处理器,并且将这个逻辑处理器绑定到唯一的操作系统线程
10.逻辑处理器会将相应的goroutine放入本地运行队列
11.本地运行队列中的goroutine会一直等待直到自己被分配到逻辑处理器执行
12.当goroutine执行一个阻塞的系统调用,其对应线程会从逻辑处理器上分离,该线程被阻塞,等待系统调用的返回
13.Go调度器会为失去线程的逻辑处理器创建一个新线程,并将新线程绑定到该逻辑处理器上
14.系统调用执行完成并返回,对应的goroutine会放回到本地运行队列,而之前的线程会被保存后,以便后用
goroutine
- runtim包中的GOMAXPROCS(num)函数可以设置分配给Go调度器使用的逻辑处理器的数量
- runtime包中的NumCPU()可以获取可以使用的物理处理器数量
- sync包中WaitGroup类型,通过它的add方法可以计数,Done方法来减少计数,Wait方法来等待计数结束
竞争状态(race condition)
- 定义:如果两个或多个goroutine在没有相互同步的情况下,访问某个共享的资源,并试图同时读和写这个资源,就处于相互竞争的状态
- Go语言有一个工具可以在代码里检测竞争状态:go build -race
- gopl-zh的翻译者翻译成了竞争条件,反而不好理解
锁住共享资源(竞争状态的处理方法)
- sync/atomic包中原子函数
- sync包中的互斥锁(gopl还提到了读写锁)
- 通道方法:
无缓冲通道
有缓存通道
特别说明:当通道关闭后,goroutine依旧可以从通道接收数据,但是不能再向通道里发送数据
并发模式
runner
- runner包用于展示如何使用通道来监视程序的执行时间,如果程序运行时间太长,也可以用runner包来终止运行
- 笔者理解:使用Runner模式来执行一系列串行任务,可以设置运行的时间,还可以终止运行
- corn作业 :crontab命令常见于Unix和类Unix的操作系统之中,用于设置周期性被执行的指令。该命令从标准输入设备读取指令,并将其存放于“crontab”文件中,以供之后读取和执行。该词来源于希腊语chronos(χρ?νο?),原意是时间。通常,crontab储存的指令被守护进程激活,crond常常在后台运行,每一分钟检查是否有预定的作业需要执行。这类作业一般称为cron jobs。
pool
- pool包用于展示如何使用有缓冲的通道实现资源池,来管理可以在任意数量的goroutine之间共享及独立使用的资源
- Go1.5版本并没有pool包,而1.6及以后版本中,实现标准库sync.Pool资源池,推荐使用
work
- work包的目的是展示如何使用无缓冲的通道来创建一个goroutine池,这些goroutine执行并控制一组工作,让其并发执行
小结
- 可以使用通道来控制程序的生命周期
- 带default分之的select语句可以用来尝试向通道发送或者接收数据,而不会阻塞
- 有缓冲的通道可以用来管理一组可复用的资源
- 语言运行时会处理好通道的协作和同步
- 使用无缓冲的通道来创建完成工作的goroutine池
- 任何时间都可以用无缓冲的通道来让两个goroutine交换数据,在通道操作完成时一定保证对方接收到数据
Go语言实战读书笔记(2):并发
原文:https://www.cnblogs.com/laiyuanjing/p/11291658.html