序言

每种编程语言都有其独特的语法,而特定的语法也反映了该编程语言被创建之初的意图,即为了解决某种旧语言的一些痛点。比如说Golang的发明,Google公司最开始还是使用C++来做工程开发,但C++有一些明显的痛点,比如说编译速度很慢,大型项目的一次构建长达2小时以上,同时还存在内存泄漏的风险,其次对并发的支持也不是很好,那么Google的几个大佬在C++语言的基础上,进行了一些修正,具体可见于Rob Pike关于go设计思路的那封邮件。

本文不想直接的罗列出Golang的一堆高级特性,然后依次讲解,而想换一种思路,结合大牛们基于Go语言实现的设计模式的代码,让大家直观的感受Go语言高级特性实战的用法和Golang的设计哲学,less is more!

简单工厂模式

语法关键词:接收者函数,接口类型interface,空结构体struct{}

那么开始,我们实现一个简单工厂模式,它的格式一般是通过入参枚举的不同,返回特定的类,然后这些类都是有一个同名函数的。比如说苹果,橘子,桃子三个产品,然后要有一个show函数告诉我一斤多少钱啊,如果要是C++写的话,那肯定要定义一个基类和一个纯虚函数,子类重写该函数即可。

但是Go是没有类的概念,当然也就没有类构造函数和类方法,对于Go来说只有普通的函数,这省去了复杂的继承和派生的逻辑,Go设计认为用正交组合的方式去组织代码要更加简单解耦。Go为了实现类似类方法和多态的感觉,引入了接收者函数和接口类型interface,接收者函数比C++那种普通函数声明多了一处不同的就是函数名前面多了一个叫接收器的东西,它定义这些函数将在其上运行的对象的一种约定格式,即类似Java接口的感觉:

那么,我们看Go语言具体如何实现简单工厂模式:

单例模式

语法关键词:包内函数和变量的私有化,sync.Once,协程,chan,等待组

单例模式的实现老生常谈啦,一面的时候总考,我一波构造函数私有化+互斥锁+double check全给他防出去了,那么Go语言实现这个模式会是怎样的呢?首先函数私有的问题,Go语言不像C++有 public、protected、private 等访问控制修饰符,其次Go舍弃了C++中include头文件的习惯,而是引入类似于python的import包,因此,Go是通过字母大小写以及下划线开头来控制可见性的,大写字母开头表示能被其它包访问或调用(相当于 public),非大写开头表示只能在包内使用(相当于 private)

这样我们就可以把单例的结构体搞成小写的,这样外面引用的人只能使用我们的公开的构造函数去创建对象,而不会直接自己就可以new啦。

其次,只执行一次是怎么做到呢?答案是用到了sync.Once这个特性。sync.Once 是 Go 标准库提供的使函数只执行一次的实现,可以在代码的任意位置初始化和调用,因此可以延迟到使用时再执行,并发场景下是线程安全的。

到了这步,单例模式的Go实现呼之欲出了,但是我们如何测试并发的场景呢,即同时要有多个线程让他都调用构造函数得到一个金斧子,然后等他们都得到一个金斧子单例的时候(这要考虑同步过程),再验证这些金斧子都是同一把呢?

并发的解决方法便是利用go协程,它是在应用层模拟的线程,他避免了上下文切换的额外耗费,兼顾了多线程的优点,这也是应了Golang的设计之初的目的就是为了解决C++并发的复杂性。go协程开启通过关键字 go 启用多个协程,然后在不同的协程中完成不同的子任务,这些用户在代码中创建和维护的协程本质上是用户级线程,用户代码的执行最后还是要落到系统级的线程中的,其内部是维护了一组数据结构, n 个线程和一个待执行队列。协程的切换是golang利用系统级异步 io函数的封装,这些封装的函数提供给应用程序使用,当这些异步函数返回 busy 或 bloking 时,golang 利用这个时机将现有的执行序列压栈,让线程去拉另外一个协程的代码来执行,这达到的协程切换的目的。

由于golang是从编译器和语言基础库多个层面对协程做了实现,所以,golang的协程是目前各类有协程概念的语言中实现的最完整和成熟的。十万个协程同时运行也毫无压力。

好啦,现在并发的条件有了,那么我们需要有协程同步的机制,大家得凝成一个势力才可以发挥更大的能量,这里利用的是特性是chan等待组,等待组和C++中的信号量很像,通过PV操作达到同步的机制。chan是Go中专属的叫通道,它是一个先进先出的队列,入队和出队会阻塞的作用,他的声明分为以下四种:

一个chan的小李子:

准备工作都做好了,那么让我们开始写一个单例模式吧。

Reference