现如今Golang在后端大火,介绍Golang的文章层出不穷,然而,很少有能跳出职能划分,从新的角度看待Golang的文章,既然如此,小的可以不自量力,来试一试了
首先,为什么要站在前端的角度考虑Golang呢?
因为Golang的原生异步并发能力
这和前端有什么关系?
先不急,我们来看一个例子:
这是一段 React 代码,那么我们再来看一段:
这是一段Golang代码,有没有感觉?
我们先抛开为什么这两段代码在逻辑上神似的问题(顺便忽略未close的deadlock),先来了解一下后端异步的问题
传统单机后端,并不需要异步,甚至并不存在异步
为何如此说?
以 Nodejs 为例,什么时候会用到异步呢?
ReadFile?WriteFile?createReadFileStream?
现实是,很多时候大家直接写作:
而不会去写这样的代码:
就这还是 Promise 化流的结果
当年Nodejs在被创作出来的时候,作者即表示,js是很适合用来做ev的语言
话说?为什么是js?或者说,为什么是在前端使用的语言,适合用来做并发和异步?
因为并发,异步,在前端无处不在
低头看看你的无冲键盘,看看你的高刷新率鼠标和屏幕,再打开调试工具,看看请求的等待时间
是不是到处都是异步?
传统后端呢?
并没有大量异步的存在,长期以来,后端都是以会话为单位编程,以flask为例:
编程过程只存在一次请求中
那么问题来了:
- 针对会话分发,请求分发的异步并发处理
- 针对文件操作,请求操作的异步并发处理
哪一个效率更高呢?
500个请求来了,我是用nginx之类的工具做负载好呢?还是要精细到每一个文件,请求,等可能造成堵塞的方法去做优化呢?
即便是支持异步的框架,比如tornado和nodejs,很多时候你也用不到异步,比如Tornado,只在全局声明一次 eventloop 就给了你每个请求的高并发能力
局限在单次请求过程中编程,异步很多时候是困扰,并不会给你带来效率上的提升
即便你写出了:await readFile() 之类的代码,这实际上也是同步的,它与 readFileSync() 这样的代码并没有太大的区别
Nodejs 作者反对 Promise 也是这个原因,在事件驱动模式下,流才是正解
话说,异步为什么会是问题呢?
首先要知道什么是异步?
异步就是不同步,asynchronous 就是 not synchronous,同步指的是,你需要的数据就是当前的版本,异步指的是,你需要的数据不是当前版本,甚至早版本
就拿请求来说,数据库有个数据是:a = 100,前端的用户将a用键盘修改为 200,那么,输入框中的数据和后台接收到的数据就不同
不同就需要处理为相同,异步需要处理为同步
例如 python future await,或者 js async await 的处理方式就很自然出现了,然而这还不够,因为类似鼠标移动,键盘输入等事件,并不是只有一次返回,这种只有开始和结束状态的逻辑,无法处理所有异步的情况
很多人很自然想到:
在内存中开辟一个空间,所有线程,进程,网络,使用这个空间不就可以了?
这种思想可以称为单一数据源思想 —— 当我们只有一份数据可以修改,就不存在数据版本异步的问题
当然,普通人想得到的,基础设施(例如linux)的建设者们早就想到了,这种方案被称为——
共享内存
而使用共享内存作为异步通讯机制,称为
共享内存异步通讯
然而,共享内存通讯是非常复杂的,很多人会说,不就是开辟单独的内存空间么,有什么难的?
但是类似文件操作,网络操作等,都是操作系统的工作,如果应用和操作系统内核共享同一段内存,那这个系统的安全性,可靠性就基本爆炸了
所以,共享内存会存在用户态和内核态两种状态,内核态时,只有内核可以操作,用户态时,应用可以进行读写
除此之外,写入读写的并发问题,哲学家吃饭问题,都是处理异步过程中的烦恼
为了解决这些问题,有很多异步机制被提出来,比如poll,epoll模型等
而linux从内核层次支撑了这一部分:
以一个4g的linux系统的内存为例,除了头尾的部分,中间出现了堆区和栈区
而二者的中间,出现了一段“内存映射片段”:
内存映射&#xff0c;就是将用户空间的一段内存区域映射到内核空间&#xff0c;映射成功后&#xff0c;用户对这段内存区域的修改可以直接反映到内核空间&#xff0c;同样&#xff0c;内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间<---->用户空间两者之间需要大量数据传输等操作的话效率是非常高的。
而网关&#xff0c;文件之类资源&#xff0c;可以用文件描述符&#xff08;fs&#xff09;进行访问
因此&#xff0c;可以实现内核与应用&#xff0c;应用与应用&#xff0c;线程间&#xff0c;进程间甚至是计算机与计算机之间的高效异步
相信前端程序员们已经发现了自己熟悉的堆栈&#xff0c;类似复杂数据存在堆中&#xff0c;简单数据存在栈中之类的描述
正常来说&#xff0c;这种异步方案人们会很自然地用事件驱动模式来思考&#xff0c;即&#xff0c;先后逻辑顺序&#xff0c;毕竟中间内核处理的时间&#xff0c;其他计算单元是无法介入的
然而&#xff0c;利用一些方法&#xff0c;是可以使用 响应式数据驱动 的模式思考同样一个问题的
如果只是简单的将内存划分为堆栈&#43;mmap&#xff0c;确实只能机械地按照一个个方法处理异步逻辑&#xff0c;这种方法的集合就是 “流”
然而&#xff0c;类似TCMalloc等工具&#xff0c;通过更加精细的内存管理&#xff0c;彻底改变了这一点&#xff1a;
通过精细化的封装&#xff0c;实现这样的数据驱动高并发异步是可能的&#xff1a;
React 通过 FiberNode 精细化浏览器异步调度
同样 Golang 也是利用类似这样的思想
因此&#xff0c;留给大家的就是 Go程 &#43; 通道&#xff1a;
不难找到这些方案和前端在 React 等框架中大量使用的 hooks&#xff0c;service 等思想
但是&#xff0c;以 React 为例&#xff0c;useState() 的状态是单一的&#xff0c;而 Golang 的 chan 状态并非是单一的&#xff1a;
这是有两个缓存的 channel&#xff0c;你答应第几个&#xff0c;它就是第几个数据&#xff0c;这种模式有点类似与 generator 方案&#xff0c;然而控制数据异步的仅仅是数据源&#xff0c;并非 xxx.next
Golang的三位作者之一&#xff0c;同时也是Javascript V8引擎的作者&#xff0c;相信前端从业者会从很多api的使用上找到亲切感
Golang现在市场比较火热&#xff0c;我之前有篇文章提到过前端应该如何反内卷&#xff0c;有一条解决方案便是去更好的市场&#xff0c;更有议价权的市场中试试看&#xff0c;Golang也是一个很好的方案
千万不要提全栈&#xff0c;全栈是内卷得不能再内卷的自轻自贱