runtime包介绍
(图片来自Go语言中文网)
一些较为重要的函数介绍
func NumCPU() int
- 1
使用NumCPU方法能够获得一个本地机器的逻辑CPU个数的int类型数值
func GOMAXPROCS(n int) int
- 1
GOMAXPROCS设置可同时执行的最大CPU数,并返回先前的设置。 若 n < 1,它就不会更改当前设置。本地机器的逻辑CPU数可通过 NumCPU 查询。本函数在调度程序优化后会去掉。
一般来说,在go1.8版本之后,系统会默认使用全部逻辑CPU进行并行操作。而在go1.8之前的版本,则需要自己设置(即使用GOMAXPROCS)。有的时候设计者需要保留一些CPU的功能占用,也会自主设定GOMAXPROCS比最大可用CPU数量少一些。
拓展完毕。接下来进入正题
那么不同的Goroutine之间如何通讯呢?有两种方式:
1、全局变量互斥锁
2、管道Channel
在Go语言中倡导:“不要以共享内存的方式来通信,相反,要通过通信来共享内存。”
首先看一下传统的并发形式:
多线程共享内存,这也是Java、C#或者C++等语言中的多线程开发的常规方法,其实golang语言也支持这种传统模式。
另外一种是Go语言特有的:CSP(Communicating Sequential Processes)并发模型。不同于传统的多线程通过共享内存来通信,CSP讲究的是“以通信的方式来共享内存”。
在讲同步通信之前先来看一下最简单基础的锁机制
锁机制
锁甚至可以直接理解为同步(Synchronization)。首先了解一下为什么会需要锁机制。在读取和写入这两种方法中,读取是不需要锁的,因为可以多个线程同时读取。而写就不可以,需要加入锁(像最简单的互斥锁Mutex),防止其他的线程进行干扰。
一般情况有两种锁,一种是互斥锁, 一种是读写锁,其中加了互斥锁的程序性能比读写锁的性能要低200倍
(图片来自Go语言中文网)
举个关于互斥锁的?:有一个电影,多个人同时去看,不会出现问题(读取的线程)。但是拍电影的时候,一个场景只能由一个摄像机去拍摄(纯3D立体除外。。。),不能多个同时拍一个画面,剪切出来的也是一个摄像机拍出来的(写入的线程)。
再举一个关于读写锁的?:在大学课堂都经历过,教室里面有黑板,全班的同学都可以看黑板上面老师的板书(读), 但是能够写黑板的只有老师一个人(写)。那么如果有两个老师同时想要写黑板的话,就会造成同学们无法确定该看(读)谁写的文字。因此其中一个老师在黑板上书写文字内容的时候, 需要把这一过程上锁,一旦上了读写锁,那么第二个老师只能等待第一个老师书写完毕才可以进行书写。
锁主要是为了防止线程之间对于资源的干扰,当一个线程对本身的操作加锁之后(Lock),那么别的线程就无法访问与其相关的内容,这样不会出现干扰,像写入异常等。最经典的就是Data race,资源争夺。
锁需要引入“sync”标准包
(图片来自Go语言中文网)
由图片对于sync标准包的介绍可以看到,锁一般适用于低水平程序线程,高水平同步应该使用channel通信更好。接下来进入Go的一大特色,channel管道。
管道Channel
先说一下为什么会需要channel。在前面说可以使用加锁来同步解决goroutine的通信,但是并不完美。因为主线程如果退出的话会导致未完成工作的goroutine协程提前退出,这样会出现各种各样的问题。
在我最开始的时候想到通过sleep来获取更多时间,使协程能够运行完毕。但是如果需要运算的内容特别庞大,那么时间就不好把控。sleep时间多了会加长等待切浪费资源,少了,主线程退出goroutine退出销毁程序异常。
而且还存在一个问题,比较不容易判断出在哪里需要增加锁(lock),哪里需要解除锁(unlock)。因此,go设计了一个新的通讯机制Channel。
下面基本介绍一下channel
1、 channel的本质就是一个数据结构-队列
2、channel遵循队列的读取顺序,先进先出(FIFO:first in first out)。
3、channel的线程是很安全的!它是由编译器在底层维护的。多个goroutine对同一个channel进行操作,都可以稳定安全的运行,不出现资源竞争问题。channel本身是不需要使用锁的,但是从源码可以看出,channel本身是有锁,使它本身的机制而来(底层代码实现),我们调用的时候并不需要再去添加或解除锁。
4、channel本身是有数据类型的。string类型的channel只能存放string类型的数据。一般我们都设置为interface{}类型,这样取的时候可以不用进行类型断言去取,免除了一些不必要的麻烦。
未完待续…