1、Wild Ones 提问: 你好,大牛,golang后端开发需要掌握哪些知识和技术?有没有一些开源项目推荐? 回答: 除了语言本身,看具体哪个领域。如果使用 Go 进行 Web 开发,普通工程师的话,至少需要掌握:HTTP协议、Linux、Web Server、数据库如MySQL、Redis缓存等。这是最基本的要求。其他比如高可用、分布式方案等,可以后续慢慢涉猎。 Go 的开源项目的话,可以根据上面的,设计不同方面。看你目的是什么。 2、暗香去 提问: 相对于java,Go拥有着超高并发能力,那么Go是如何解决IO等待问题的? 回答: 借此问题,普及一下 IO 的相关知识点。 IO 基本概念 Linux 的内核将所有外部设备都可以看做一个文件来操作(Unix 的设计原则,一切皆文件)。那么我们对外部设备的操作都可以看做对文件进行操作。我们对一个文件的读写,都通过调用内核提供的系统调用;内核给我们返回一个 file descriptor(fd,文件描述符)。对一个 socket 的读写也会有相应的描述符,称为socketfd(socket 描述符)。描述符就是一个数字(可以理解为一个索引),指向内核中一个结构体(文件路径,数据区,等一些属性)。应用程序对文件的读写就通过对描述符的读写完成。 一个基本的 IO,它会涉及到两个系统对象,一个是调用这个 IO 的进程对象,另一个就是系统内核(kernel)。 一般来说,服务器端的 I/O 主要有两种情况:一是来自网络的 I/O;二是对文件(设备)的I/O。 常见的 IO 模型 首先一个 IO 操作其实分成了两个步骤:发起 IO 请求(等待网络数据到达网卡并读取到内核缓冲区,数据准备好)和实际的 IO 操作(从内核缓冲区复制数据到进程空间)。 阻塞和非阻塞 阻塞 IO 和非阻塞 IO 的区别在于第一步:发起 IO 请求是否会被阻塞。如果阻塞直到完成,那么就是传统的阻塞 IO,如果不阻塞,那么就是非阻塞 IO。 同步和异步 同步 IO 和异步 IO 的区别就在于第二个步骤是否阻塞。如果实际的 IO 读写阻塞请求进程,那么就是同步IO。因此常说的阻塞 IO、非阻塞 IO、IO 复用、信号驱动 IO 都是同步 IO。如果不阻塞,而是操作系统帮你做完 IO 操作后再将结果返回给你(通知你),那么就是异步IO。 IO 多路复用 指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。 目前支持 I/O 多路复用的常用系统调用有 select,pselect,poll,epoll 等,I/O 多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但 select,pselect,poll,epoll本质上都是同步 I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步 I/O 则无需自己负责进行读写,异步 I/O 的实现会负责把数据从内核拷贝到用户空间。 5 种 IO 模型 《UNIX 网络编程》对 IO 模型进行了总结,分别是: 阻塞 IO、非阻塞 IO、IO 多路复用、信号驱动的 IO、异步 IO;前 4 种为同步 IO,只有异步 IO 模型是异步 IO。 回到问题 目前很多高性能的基础网络服务器都是采用的 C 语言开发的,比如:Nginx、Redis、memcached 等,它们都是基于”事件驱动 + 事件回调函数”的方式实现,也就是采用 epoll 等作为网络收发数据包的核心驱动。但不少人都认为“事件驱动 + 事件回调函数”的编程方法是“反人类”的;因为大多数人都更习惯线性的处理一件事情:做完第一件事情再做第二件事情,并不习惯在 N 件事情之间频繁的切换干活。为了解决程序员在开发服务器时需要自己的大脑不断的“上下文切换”的问题,Go 语言引入了一种用户态线程 goroutine 来取代编写异步的事件回调函数,从而重新回归到多线程并发模型的线性、同步的编程方式上。 在 Linux 上 Go 语言写的网络服务器也是采用的 epoll 作为最底层的数据收发驱动,Go 语言网络的底层实现中同样存在“上下文切换”的工作,只是这个切换工作由 runtime 的调度器来做了,减少了程序员的负担。 所以,IO 等待是必然,只是谁等的问题。Go 语言在遇到 IO 需要等待时,runtime 会进行调度,语言层面处理这个问题。Go 拥有超高并发能力的关键就在于用户态的 goroutine。 注:网络编程,涉及到太多知识点,咱们后面可以找时间慢慢聊。 3、 提问: go int32和uint32区别,和使用的场景, 回答:int32 是有符号类型;uint32 是无符号类型。有符号和无符号的概念知道吧。 一般的项目,直接使用 int 即可;有一些对内存要求很高的场景,希望每个变量都尽可能做到够用不超。比如,我们存一个数字,它不会是负数,同时最大值不会超过 1<<32 - 1,那么就可以使用 uint32。 在 32 位机器上,int 和 int32 占用空间和表示的范围是一样的。 3、想问一下,对Chan类型的变量,什么时候用select,什么时候用for range 回答: 多个chan用select,单个用for range