给前端的Golang介绍

mb6004f83c7725c · · 1155 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

现如今Golang在后端大火,介绍Golang的文章层出不穷,然而,很少有能跳出职能划分,从新的角度看待Golang的文章,既然如此,小的可以不自量力,来试一试了

首先,为什么要站在前端的角度考虑Golang呢?

因为Golang的原生异步并发能力

这和前端有什么关系?

先不急,我们来看一个例子:

const [data,setData] = useState<string>("")
useEffect(()=>{  console.log(data)
},[data])复制代码

这是一段 React 代码,那么我们再来看一段:

var data = make(chan string)for range data{  println(<-data)
}复制代码

这是一段Golang代码,有没有感觉?

我们先抛开为什么这两段代码在逻辑上神似的问题(顺便忽略未close的deadlock),先来了解一下后端异步的问题


传统单机后端,并不需要异步,甚至并不存在异步

为何如此说?

以 Nodejs 为例,什么时候会用到异步呢?

ReadFile?WriteFile?createReadFileStream?

现实是,很多时候大家直接写作:

const content = ReadFileSync("xxxx")复制代码

而不会去写这样的代码:

const pipeline = util.promisify(stream.pipeline);const fs = require('fs');async function run() {  await pipeline(
    fs.createReadStream('lowercase.txt'),async function* (source) {
      source.setEncoding('utf8');  // Work with strings rather than `Buffer`s.  for await (const chunk of source) {yield chunk.toUpperCase();
      }
    },
    fs.createWriteStream('uppercase.txt')
  );  console.log('Pipeline 成功');
}

run().catch(console.error);复制代码

就这还是 Promise 化流的结果

当年Nodejs在被创作出来的时候,作者即表示,js是很适合用来做ev的语言

话说?为什么是js?或者说,为什么是在前端使用的语言,适合用来做并发和异步?

因为并发,异步,在前端无处不在

低头看看你的无冲键盘,看看你的高刷新率鼠标和屏幕,再打开调试工具,看看请求的等待时间

是不是到处都是异步?

传统后端呢?

并没有大量异步的存在,长期以来,后端都是以会话为单位编程,以flask为例:

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        # check if the post request has the file part
        if 'file' not in request.files:
            flash('No file part')
            return redirect(request.url)
        file = request.files['file']
        # if user does not select file, browser also
        # submit an empty part without filename
        if file.filename == '':
            flash('No selected file')
            return redirect(request.url)
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
            return redirect(url_for('uploaded_file',
                                    filename=filename))
    return '''
    <!doctype html>
    <title>Upload new File</title>
    <h1>Upload new File</h1>
    <form method=post enctype=multipart/form-data>
      <input type=file name=file>
      <input type=submit value=Upload>
    </form>
    '''复制代码

编程过程只存在一次请求中

那么问题来了:

  1. 针对会话分发,请求分发的异步并发处理
  2. 针对文件操作,请求操作的异步并发处理

哪一个效率更高呢?

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从内核层次支撑了这一部分:

imgimg

以一个4g的linux系统的内存为例,除了头尾的部分,中间出现了堆区和栈区

而二者的中间,出现了一段“内存映射片段”:

内存映射,就是将用户空间的一段内存区域映射到内核空间,映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间<---->用户空间两者之间需要大量数据传输等操作的话效率是非常高的。

imgimg

而网关,文件之类资源,可以用文件描述符(fs)进行访问

因此,可以实现内核与应用,应用与应用,线程间,进程间甚至是计算机与计算机之间的高效异步

相信前端程序员们已经发现了自己熟悉的堆栈,类似复杂数据存在堆中,简单数据存在栈中之类的描述

正常来说,这种异步方案人们会很自然地用事件驱动模式来思考,即,先后逻辑顺序,毕竟中间内核处理的时间,其他计算单元是无法介入的

然而,利用一些方法,是可以使用 响应式数据驱动 的模式思考同样一个问题的

如果只是简单的将内存划分为堆栈+mmap,确实只能机械地按照一个个方法处理异步逻辑,这种方法的集合就是 “流”

然而,类似TCMalloc等工具,通过更加精细的内存管理,彻底改变了这一点:

imgimg

通过精细化的封装,实现这样的数据驱动高并发异步是可能的:

React 通过 FiberNode 精细化浏览器异步调度

同样 Golang 也是利用类似这样的思想

因此,留给大家的就是 Go程 + 通道:

// 一个通道channel_1 := make(chan string)// 向通道传值channel_1 <- 333// 关闭这个通道close(channel_1)// 订阅这个通道for range channel_1{  println(<-channel_1)
}// 打开一个 go 程go func(){
  time.Sleep(time.Second * 1)
  channel_1 <- "test"}()复制代码

不难找到这些方案和前端在 React 等框架中大量使用的 hooks,service 等思想

但是,以 React 为例,useState() 的状态是单一的,而 Golang 的 chan 状态并非是单一的:

var data = make(chan string, 2)	go func() {
		time.Sleep(time.Second * 1)
		data <- "test"
		time.Sleep(time.Second * 1)
		data <- "test2"
	}()	println(<-data)	println(<-data)复制代码

这是有两个缓存的 channel,你答应第几个,它就是第几个数据,这种模式有点类似与 generator 方案,然而控制数据异步的仅仅是数据源,并非 xxx.next


Golang的三位作者之一,同时也是Javascript V8引擎的作者,相信前端从业者会从很多api的使用上找到亲切感

Golang现在市场比较火热,我之前有篇文章提到过前端应该如何反内卷,有一条解决方案便是去更好的市场,更有议价权的市场中试试看,Golang也是一个很好的方案

千万不要提全栈,全栈是内卷得不能再内卷的自轻自贱


有疑问加站长微信联系(非本文作者)

本文来自:51CTO博客

感谢作者:mb6004f83c7725c

查看原文:给前端的Golang介绍

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1155 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传