Go的IO -- Go语言设计与实现

news2025/1/20 6:02:54

Go合IO的不解之缘

协程是Go的很大的一个优势。Go天然支持高并发,那么我们来研究一下这个高并发的秘诀在哪里?

执行体调度得当。CPU 不停的在不同的执行体( Goroutine )之间反复横跳!CPU 一直在装填和运行不同执行体的指令,G1(Goroutine 1 的缩写) 不行就搞 G2 ,一刻不能停,这样才能使得大量的执行体( Goroutine )齐头并进,系统才能完成如此高并发的吞吐(注意关键字:并发哦)。

思考一个问题,程序可以分成CPU密集型合IO密集型,Go适合哪一种?

很明显IO密集型,CPU密集型只能买核,买CPU,别无他法。

为什么适合IO密集型。

因为IO设备核CPU是独立的设备,这两者是可以并行运行的。

但是不是所有的IO都适配Go的高并发特性。

IO又分为:网络IO和文件IO。最适合Go的是网络IO密集型的程序。

  1. 网络IO的fd可以用epoll池来管理事件,实现异步IO
  2. 文件IO的fd不能用epoll来管理事件,只能进行同步IO

如果想让文件IO实现异步IO,在当前Linux有两种方式:

  • Linux系统提供的AIO:但是Go没有封装实现。
  • Linux系统提供的io_uring:内核版本要求高,线上没有普及。

一句话,Go 语言级别把网络 IO 做了异步化,但是文件 IO 还是同步的调用。

Go中最原本的IO库

基础类型

比如:ReaderWriterCloserReaderAtWriterAtSeekerByteReaderByteWriterRuneReaderStringWriter 等;

组合类型

第二大类就是组合类型,往往是把最基本的接口组合起来,使用的语法糖则是 Go 的匿名字段的用法,比如:ReaderCloserWriteCloserWriteSeeker 等;

type ReadWriter interface {
 Reader
 Writer
}

进阶类型

这个类型比较有意思,一般是基于基础接口之上,加了一些有趣的实现,比如:

TeeReaderLimitReaderSectionReaderMultiReaderMultiWriterPipeReaderPipeWriter 等;

IO通用的函数

  • Copy :把一个 Reader 读出来,写到 Writer 里去,直到其中一方出错为止( 比如最常见的,读端出现 EOF );
  • CopyN :这个和 Copy 一样,读 Reader,写 Writer,直到出错为止。但是 CopyNCopy 多了一个结束条件:数据的拷贝绝对不会超过 N 个;
  • CopyBuffer:这个也是个拷贝实现,和 CopyCopyN 本质无差异。这个能让用户指定使用多大的 Buffer 内存,这个可以让用户能根据实际情况优化性能,比如大文件拷贝的话,可以考虑使用大一点的 buffer,提高效率( 1G 的文件拷贝,它也是分了无数次的读写完成的,比如用 1M 的内存 buffer,不停的搬运,搬运 1024 次,才算完)。

io/ioutil工具库

这个库位于 src/io 目录之下,目录路径为 src/io/ioutil。顾名思义,这是一个工具类型的库,util 嘛,你们都懂的,啥都有,是一些方便的函数实现。他的核心是:怎么方便怎么来。

  • ReadFile:给一个路径,把文件一把读到内存(不需要 open,close,read,write 啥的,统统封装起来)方便不?
  • WriterFile:给一个路径,把内存一把写入文件,方便不?
  • ReadDir:给一个目录路径,把这个路径下的文件列表一把读上来,方便不?
  • ReadAll:给一个 Reader 流,一把读完,全部读到内存,方便不?

这就是个工具库,就是应付一些简单的场景的 IO 方便而已。注意了,场景一定要简单,举个栗子:

使用 ReadAll 这个函数,是把 Reader 流全部读到内存,所以这里内存要装得下才行,如果你这个 Reader 是从一个 2 T 的文件来的,那就 (⊙o⊙)… 尴尬了。

IO库的拓扑

在这里插入图片描述

io 和字节的故事:bytes 库

处理字节数组的库,bytes.Reader 可以把 []byte 转换成 Readerbytes.Buffer 可以把 []byte 转化成 ReaderWriter ,换句话讲,内存块可以作为读写的数据流了。

io 和网络的故事:net 库

网络可以作为读写源,抽象成了 ReaderWriter 的形式。这个是以 net.Conn 封装出来的。

举个栗子:演示一个 C/S 通信。

服务端:

package main

import (
	"log"
	"net"
)

func handleConn(conn net.Conn) {
	defer conn.Close()
	buf := make([]byte, 4096)
	conn.Read(buf)
	conn.Write([]byte("pong: "))
	conn.Write(buf)
}

func main() {
	server, err := net.Listen("tcp", ":8888")
	if err != nil {
		log.Println(err)
	}
	for {
		c, err := server.Accept()
		if err != nil {
			log.Println(err)
		}
		go handleConn(c)
	}
}
  • net.Listen 创建一个监听套接字,在 Go 里封装成了 net.Listener 类型;
  • Accept 函数返回一个 net.Conn ,代表一条网络连接,net.Conn 既是 Reader,又是 Writer ,拿到之后各自处理即可;

客户端:

func main() {
	conn, err := net.Dial("tcp", ":9999")
	if err != nil {
		panic(err)
	}
	conn.Write([]byte(""))
	io.Copy(os.Stdout, conn)
}
  1. net.Dail 传入服务端地址和网络协议类型,即可返回一条和服务端通信的网络连接,返回的结构为 net.Conn
  2. net.Conn 即可作为读端( Reader ),也是写端( Writer );

以上无论是 net.Listener ,还是 net.Conn 都是基于系统调用 socket 之上的一层封装。底下使用的是类似的系统调用:

syscall.Socket
syscall.Connect
syscall.Listen
syscall.GetsockoptInt
  1. 创建还是用 socket 的调用创建的 fd,创建出来就会立马设置 nonblock 模式(因为 Go 的网络 fd 天然要使用 IO 多路复用的方式来走 IO ),还有其他配置;
  2. 把 socket fd 丢到 epoll 池里 ( 通过 poll.runtime_pollOpen 把 socket 套接字加到 epoll 池里,底层调用的还是 epollctl ),监听事件;
  3. 封装好读写事件到来的函数回调;

io和文件的故事:os库

文件 IO,这个就是我们最常见的文件 IO 啦,文件可以作为读端,也可以作为写端。

File

io 的读写端可以是文件。这个太容易理解了,也是我们最常见的读写端,毕竟文件就是存储数据的一种形态。在 Go 里面,我们用 os.OpenFile 这个调用,就可以获取到 Go 帮你封装的文件操作句柄 FileFile 这个结构体对外实现了 ReadWriteReadAtWriteAt 等接口,所以自然就可以作为 ReaderWriter 来使用。 。

举个栗子:

    // 如下,把 test.data 的数据读出来丢到垃圾桶
    fd, err := os.OpenFile("test.data", os.O_RDWR, 0)
    if err != nil {
        panic(err)
    }
    io.Copy(ioutil.Discard, fd)

这里返回了一个 File 类型,不难想象这个是基于文件 fd 的一层封装。这个里面大概做了什么?

  1. 调用系统调用syscall.Open 拿到文件 fd ,顺便设置了下垃圾回收时候的析构函数,其他的好像没了。远比网络 fd 要简单

Stdin、Stdout、Stderr

Go 这个把标准输入、标准输出、标准错误输出 抽象成了读写源,对应了 os.Stdinos.Stdoutos.Stderr 这三个变量。这三个变量其实就是 File 类型的变量,定义在 Go 的源码库 src/os/file.go 里:

var (
 Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
 Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
 Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)

划重点:这个就是我们常用的 0,1,2 句柄哦。

标准输入就可以和方便的作为读端( Reader ),标准输出可以作为写端( Writer )啦。

举个栗子:用一行代码实现一个最简单的 echo 回显的程序。

func main() {
    // 一行代码实现回显
    io.Copy(os.Stdout, os.Stdin)
}

把标准输入作为读端,标准输出作为写端。编译出来运行吧,在终端随便输入一个字符串,敲下回车看下效果吧。

缓存与io的故事:bufio库

Reader/Writer 可以是缓冲 IO 的数据流

在 c 语言,有人肯定用过 fopen 打开的文件(所谓的标准IO):

FILE * fopen ( const char * filename, const char * mode );

这个函数 open 出的是一个 FILE 结构体,而非之前常说的整数 fd 句柄。通过这个文件句柄的 IO 就是标准库帮你实现的缓冲 IO。c 语言的缓冲 IO 有三种模式:

  • 全缓冲:数据填满 buffer,才会真正的调用底层 IO;
  • 行缓冲:不用等填满 buffer,遇到换行符,就会把 IO 下发下去;
  • 不缓冲:bypass 的模式,每次都是直通底层 IO;

Go 的缓冲 IO 则是由 bufio 这个库提供。先讨论下缓冲 IO 究竟是什么吧。

缓冲 IO 是在底层 IO 之上实现的一层 buffer ,什么意思?

假设有个用户,每次写数据都只写 1 个字节,顺序的,写 512 次。之前我们在磁盘 IO 为啥要对齐中提过,非对齐的 IO 性能损失很大。以普通的机械硬盘来说,写 1 个字节,其实要先读一个扇区出来,然后再写下去。所以这里 IO 实际的 IO 次数为 1024 次,实际的 IO 数据量为:读 512 *512 Byte,写了 512 *512 Byte。

能怎么优化呢?

搞一个内存 512 字节的 buffer ,用户写 1 个字节我就先暂存在 buffer 里面,直到写满 512 字节,buffer 满了,然后一次性把 512 字节的 buffer 数据写到底层。你会发现,这里实际的 IO 只有一次,实际的数据量只有 512 字节。这就是 buffer io ,能极大的减少底层真实的 IO 次数。

所以,缓冲 IO 的优势是什么?

一目了然,写的时候能聚合 IO,极大减少 IO 次数。读的时候还能实现预读的效果,同样也减少 IO 次数。

这个也很容易理解,buffer io 相当于缓存数据了,一份数据多份存储了,这里给数据的一致性管理带来了复杂性,预读还有可能读到脏数据等等混乱情况。

bufio 这个库我们先理解名字,buffer io 的缩写。那顾名思义,核心是实现了缓存 IO 的库。对一个 Reader/Writer 携带一个内存 buffer 做了一层封装,达到聚合 io 的目的。

img

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/414626.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数字化坚鹏:金融数据治理、数据安全政策解读及银行数字化转型

金融数据治理、数据安全政策解读及银行数字化转型课程背景: 很多银行存在以下问题: 不知道如何准确理解金融数据治理及数据安全相关政策 不清楚金融数据治理及数据安全相关政策对银行有什么影响? 不清楚如何进行银行数字化转型&#xff1f…

Azure DevOps Pipelines

Azure DevOps主要通过管理代码、管理服务器、管理发布的管道来实现一体化解决方案 发布流程: 1、代码上传Repos仓储 略 2、DevOps连接并管理发布服务器 2.1、Deployment Groups配置 2.2、服务器执行连接指令 2.3、服务器状态查看 3、创建 Pipline(构建代码) 3.1…

前端中font的使用

知识点&#xff1a; 运行截图&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta http-equiv"X-UA-Compatible" content"IEedge"> <meta name&…

【RabbitMQ】SpringBoot整合RabbitMQ、实现RabbitMQ五大工作模式(万字长文)

目录 一、准备 1、创建SpringBoot项目 2、添加配置信息 3、创建配置类 二、RabbitMQ的配置类里创建队列 三、RabbitMQ的配置类里创建交换机及绑定队列 四、SpringBoot整合RabbitMQ入门案例 1、生产者 2、消费者 四、SpringBoot里实现RabbitMQ五大工作模式 1、简单模式…

Linux--进程多线程(上)

前言 精神内耗一方面可能是消极的&#xff0c;人好像一直在跟自己过不去&#xff0c;但其实它也是一种积极的情绪。精神内耗在某种程度上&#xff0c;是在寻找一种出口&#xff0c;寻找他自己人生的出口&#xff0c;寻找我今天的出口&#xff0c;或者寻找我一觉醒来明天的出口。…

【k8s完整实战教程5】网络服务配置(nodeport/loadbalancer/ingress)

系列文章&#xff1a;这个系列已完结&#xff0c;如对您有帮助&#xff0c;求点赞收藏评论。 读者寄语&#xff1a;再小的帆&#xff0c;也能远航&#xff01; 【k8s完整实战教程0】前言【k8s完整实战教程1】源码管理-Coding【k8s完整实战教程2】腾讯云搭建k8s托管集群【k8s完…

恐怖的ChatGPT!

大家好&#xff0c;我是飞哥&#xff01;不知道大家那边咋样。反正我最近感觉是快被ChatGPT包围了。打开手机也全是ChatGPT相关的信息&#xff0c;我的好几个老同学都在问我ChatGPT怎么用&#xff0c;部门内也在尝试用ChatGPT做一点新业务出来。那就干脆我就趁清明假期这一天宝…

AB测试基本原理

AB测试基本原理AB测试AB测试的基本步骤1、AB测试的基本步骤①选取指标指标的分类②建立假设③选取实验单位④计算样本量⑤流量分割⑥实验周期计算⑦线上验证⑧数据检验AB测试 所谓的AB测试就是使用实验组和对照组&#xff0c;通过控制变量法保证实验组和对照组基本条件一致&am…

NumPy 数组学习手册:6~7

原文&#xff1a;Learning NumPy Array 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 六、性能分析&#xff0c;调试和测试 分析&#xff0c;调试和测试是开发过程的组成部分。 您可能熟悉单元测试的概念。 单元测试是程序员编写的用于测试其代码的自动测试。 例如&…

AI —— 一看就懂的代码助手Copilot获取教程

背景 随着chatgpt的发布&#xff0c;人工智能领域近期站上了风口浪尖。GitHub Copilot由github与 OpenAI 合作创建&#xff0c;是世界上第一个使用 OpenAI 的 Codex 模型&#xff08;GPT-3 的后代&#xff09;制作的大规模生成式 AI 开发工具。GitHub Copilot 作为 AI 结对程序…

【条件判断】

目录知识框架No.0 筑基No.1 条件判断题目来源&#xff1a;PTA-L1-031 到底是不是太胖了题目来源&#xff1a;PTA-L1-063 吃鱼还是吃肉题目来源&#xff1a;PTA-L1-069 胎压监测题目来源&#xff1a;PTA-L1-077 大笨钟的心情题目来源&#xff1a;PTA-L1-083 谁能进图书馆知识框架…

Day15-二维数组字符串

文章目录一 二维数组二 字符串案例1案例2案例3-随堂练习案例4-输入-类型转换案例5案例6案例7一 二维数组 <script>// 书:编号 名称 描述 价格/*** 二维数组* */let arr [ [1001,"HTML","网页设计",100],[1002,"CSS","样式设计"…

Leetcode刷题之环形链表

莫等闲&#xff0c;白了少年头&#xff0c;空悲切。 --岳飞 目录 1.环形链表 2.环形链表Ⅱ 1.环形链表 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next …

Stable Diffusion扩散模型

1 GAN到Stable Diffusion的改朝换代 随着人工智能在图像生成&#xff0c;文本生成以及多模态生成等生成领域的技术不断累积&#xff0c;生成对抗网络&#xff08;GAN&#xff09;、变微分自动编码器&#xff08;VAE&#xff09;、normalizing flow models、自回归模型&#xf…

Android Textview Button 等基础组件学习

一 Textview 1 基本使用 <?xml version"1.0" encoding"utf-8"?><LinearLayout android:layout_height"match_parent"android:layout_width"match_parent"android:orientation"vertical"xmlns:android"http…

vue中的pinia使用和持久化 - 粘贴即用

学习关键语句&#xff1a; pinia怎么用 写在前面 pinia 作为 vuex 的替代品好像变得不得不学习了&#xff0c;学起来一用发现 vuex 是什么麻烦的东西&#xff0c;我不认识 这篇文章一共包含的内容有&#xff1a; 安装 pinia读取数据修改数据数据持久化 其中&#xff0c;修…

代码不熟没关系,让AI替你写

程序员早已不是一个陌生的群体&#xff0c;但程序、代码相对普通人而言&#xff0c;看着还是比较深奥难懂&#xff0c;但自从有了ChatGPT&#xff0c;不少对此有兴趣的外行人士&#xff0c;也能轻松写出代码了&#xff0c;比如让ChatGPT写一个贪吃蛇游戏&#xff0c;按它给出的…

C++入门(2)

C入门1.缺省参数1.1. 缺省参数举例和概念1.2. 函数的传参是从左到右给参数的1.3.缺省参数分类1.4. 缺省参数的函数声明与定义2.函数重载2.1.函数重载的概念2.2. 函数重载的情况2.3.剖析C语言不能函数重载而C却可以的原因2.3.1. 编译链接过程2.3.2. 函数名修饰规则3.引用3.1. 引…

Java并行流:一次解决多线程编程难题,让你的程序飞起来

前言 在日常的工作中&#xff0c;为了提高程序的处理速度&#xff0c;充分利用多核处理器的性能&#xff0c;我们需要手动编写多线程代码。但是多线程编程非常复杂&#xff0c;容易出现死锁、竞态条件等问题&#xff0c;给我们带来了很大的困扰。而 Java 并行流则提供了一种更加…

python机器学习和深度学习在气象中的应用

查看原文>>> Python人工智能在气象中的实践技术应用 Python 是功能强大、免费、开源&#xff0c;实现面向对象的编程语言&#xff0c;在数据处理、科学计算、数学建模、数据挖掘和数据可视化方面具备优异的性能&#xff0c;这些优势使得 Python 在气象、海洋、地理、…