坚持思考,就会很酷
Go 是一种高性能的编译性语言,天然支持高并发,语言级别封装协程,号称可以支持成千上万,十万,甚至百万的协程并发。这个量级远不是线程可比的。
前置小知识点:并行和并发的区别?
并行看的是时刻,并发看的是时间段。
好,先回归主题,Go 除了 Goroutine 足够轻量之外,支持如此高并发的这其中的秘诀在哪里?
执行体调度得当。CPU 不停的在不同的执行体( Goroutine )之间反复横跳!CPU 一直在装填和运行不同执行体的指令,G1(Goroutine 1 的缩写) 不行就搞 G2 ,一刻不能停,这样才能使得大量的执行体( Goroutine )齐头并进,系统才能完成如此高并发的吞吐(注意关键字:并发哦)。
思考一个问题,程序可分为 CPU 密集型的程序和 IO 密集型的程序两种,Go 适合哪种?
IO 密集型。
这里的理由其实很有意思,先说为什么不合适 CPU 密集型呢?
理由其实很简单,CPU 密集型就意味着每个执行体都是急需 CPU 的,G1 吃 CPU 都不够,还切到 G2 去干嘛?所以 CPU 密集型的程序最好的情况就是不调度! 绑核都来不及呢。想要提高这种程序的性能,就是加钱,买核,买 CPU,买 GPU,把 CPU 核并行起来。
为什么 IO 密集型就好了?
划重点:IO 设备和 CPU 可是不同的独立设备呀。 这两者之间的处理可是并行运行的。
Go 程序的协程调度则可以很好的利用这个关系,让 CPU 执行程序指令,只负责发送 IO ,一旦 IO 被 IO 设备接收,不等待完成,就可以处理其他人的指令,IO 的完成以异步事件的形式触发。这样 IO 设备的处理过程就和 CPU 的执行并行了起来。
任何 IO 都适配 Go 吗?
不是的。IO 也分为网络 IO 和文件 IO 。其实适合 Go 的程序类型准确的来讲是:网络 IO 密集型的程序。
其中差异就在于:
题外话:文件要实现异步 IO 当前在 Linux 下有两种方式:
一句话,Go 语言级别把网络 IO 做了异步化,但是文件 IO 还是同步的调用。
异步 IO 咱们就不展开了,以后单独讲。笔者铺开了这么多,只想说明一个事情:Go 和 IO 天然就存在不解之缘,IO 操作是 Go 的最核心之一。
今天就是为了简单讲讲 Go 的 IO 使用有哪些姿势。
以下 Go 源码涉及基于:Go 1.13.5
IO 无非就是读写。Go 的语义接口用 interface 来表述,定义在 io 这个 package 中,代码位置:io/io.go
。
读操作该是什么样?该传什么参数?返回什么值?
type Reader interface {
Read(p []byte) (n int, err error)
}
写操作是什么样的?该传什么参数?返回什么值?
type Writer interface {
Write(p []byte) (n int, err error)
}
**io 是 Go 里面最最核心的 IO 相关的库。**为什么怎么说呢?
因为 IO 的 interface 定义在这个地方。该库不涉及具体的 IO 实现,定义的是最本源的 IO 接口语义。
什么是 Reader
、Writer
、ReaderAt
、WriterAt
,在这个库里说的明明白白。
划重点:这个库不涉及具体的 io 实现,描述的是最核心的 IO 接口语义。
io 库的内容,如果按照接口的定义维度大致可以分为 3 大类:
基础类型:
比如:Reader
、Writer
、Closer
、ReaderAt
、WriterAt
、Seeker
、ByteReader
、ByteWrieter
、RuneReader
、StringWriter
等;
这些接口是最基本的接口,描述了最原始的 Go 的 IO 的样子,这个非常非常非常重要。重要的说三遍。
如果你写 Go 代码的时候,要实现这些接口,千万要注意在标准库里的注释。
如果大意疏忽了这些接口的语义,可能会造成不可挽回损失。笔者在 Go Reader,ReaderAt 的区别 一文中分享过一个经验案例。
组合类型:
第二大类就是组合类型,往往是把最基本的接口组合起来,使用的语法糖则是 Go 的匿名字段的用法,比如:ReaderCloser
、WriteCloser
、WriteSeeker
等;
type ReadWriter interface {
Reader
Writer
}
进阶类型:
这个类型比较有意思,一般是基于基础接口之上,加了一些有趣的实现,比如:
TeeReader
、LimitReader
、SectionReader
、MultiReader
、MultiWriter
、PipeReader
、PipeWriter
等;
比如:
TeeReader
这是一个分流器的实现,如果把数据比作一个水流,那么通过 TeeReader
之后,将会分叉出一个新的数据流。LimitReader
则是给 Reader
加了一个边界期限。MultiReader
则是把多个数据流合成一股。这个和 Linux 的 tee 命令异曲同工,细品。
io 库还有一些基于这些接口的函数,比如:
Copy
:把一个 Reader
读出来,写到 Writer
里去,直到其中一方出错为止( 比如最常见的,读端出现 EOF );CopyN
:这个和 Copy
一样,读 Reader
,写 Writer
,直到出错为止。但是 CopyN
比 Copy
多了一个结束条件:数据的拷贝绝对不会超过 N 个;CopyBuffer
:这个也是个拷贝实现,和 Copy
,CopyN
本质无差异。这个能让用户指定使用多大的 Buffer 内存,这个可以让用户能根据实际情况优化性能,比如大文件拷贝的话,可以考虑使用大一点的 buffer,提高效率( 1G 的文件拷贝,它也是分了无数次的读写完成的,比如用 1M 的内存 buffer,不停的搬运,搬运 1024 次,才算完)。这个库位于 src/io
目录之下,目录路径为 src/io/ioutil
。顾名思义,这是一个工具类型的库,util 嘛,你们都懂的,啥都有,是一些方便的函数实现。他的核心是:怎么方便怎么来。
ReadFile
:给一个路径,把文件一把读到内存(不需要 open,close,read,write 啥的,统统封装起来)方便不?WriterFile
:给一个路径,把内存一把写入文件,方便不?ReadDir
:给一个目录路径,把这个路径下的文件列表一把读上来,方便不?ReadAll
:给一个 Reader
流,一把读完,全部读到内存,方便不?这就是个工具库,就是应付一些简单的场景的 IO 方便而已。注意了,场景一定要简单,举个栗子:
使用 ReadAll
这个函数,是把 Reader
流全部读到内存,所以这里内存要装得下才行,如果你这个 Reader
是从一个 2 T 的文件来的,那就 (⊙o⊙)… 尴尬了。
io 库定义了 io 的该有的样子。现在可以想象具体的问题了,Reader
,Writer
可能是哪些?
都可以!Go 帮你做好了这一切。
IO 行为都是以 io 库为中心发散的。
围绕着 IO 库,io 的姿势丰富多彩,io 的主体随意定制。
Reader/Writer 可以是内存字节数组。
处理字节数组的库,bytes.Reader
可以把 []byte
转换成 Reader
,bytes.Buffer
可以把 []byte
转化成 Reader
、Writer
,换句话讲,内存块可以作为读写的数据流了。
举个栗子:
buffer := make([]byte, 1024)
readerFromBytes := bytes.NewReader(buffer)
n, err := io.Copy(ioutil.Discard, readerFromBytes)
// n == 1024, err == nil
fmt.Printf("n=%v,err=%v\n",n, err)
字符串可以是 Reader。
strings.Reader
能够把字符串转换成 Reader
,这个也挺有意思的,直接能把字符串作为读源。
data := "hello world"
readerFromBytes := strings.NewReader(data)
n, err := io.Copy(ioutil.Discard, readerFromBytes)
fmt.Printf("n=%v,err=%v\n",n, err)
网络可以作为读写源,抽象成了 Reader
、Writer
的形式。这个是以 net.Conn
封装出来的。
举个栗子:演示一个 C/S 通信。
服务端:
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", ":9999")
if err != nil {
log.Fatalf("err:%v", err)
}
for {
c, err := server.Accept()
if err != nil {
log.Fatalf("err:%v", err)
}
go handleConn(c)
}
}
关键点:
net.Listen
创建一个监听套接字,在 Go 里封装成了 net.Listener
类型;Accept
函数返回一个 net.Conn
,代表一条网络连接,net.Conn
既是 Reader,又是 Writer ,拿到之后各自处理即可;客户端:
通过 net.Dail
一个 net.Conn
出来。
func main() {
conn, err := net.Dial("tcp", ":9999")
if err != nil {
panic(err)
}
conn.Write([]byte("hello world\n"))
io.Copy(os.Stdout, conn)
}
关键点:
net.Dail
传入服务端地址和网络协议类型,即可返回一条和服务端通信的网络连接,返回的结构为 net.Conn
;net.Conn
即可作为读端( Reader
),也是写端( Writer
);以上无论是 net.Listener
,还是 net.Conn
都是基于系统调用 socket 之上的一层封装。底下使用的是类似的系统调用:
syscall.Socket
syscall.Connect
syscall.Listen
syscall.GetsockoptInt
Go 针对网络 fd 会做哪些封装呢?
nonblock
模式(因为 Go 的网络 fd 天然要使用 IO 多路复用的方式来走 IO ),还有其他配置;poll.runtime_pollOpen
把 socket 套接字加到 epoll 池里,底层调用的还是 epollctl
),监听事件;文件 IO,这个就是我们最常见的文件 IO 啦,文件可以作为读端,也可以作为写端。
io 的读写端可以是文件。这个太容易理解了,也是我们最常见的读写端,毕竟文件就是存储数据的一种形态。在 Go 里面,我们用 os.OpenFile
这个调用,就可以获取到 Go 帮你封装的文件操作句柄 File
,File
这个结构体对外实现了 Read
,Write
,ReadAt
,WriteAt
等接口,所以自然就可以作为 Reader
和 Writer
来使用。。
举个栗子:
// 如下,把 test.data 的数据读出来丢到垃圾桶
fd, err := os.OpenFile("test.data", os.O_RDWR, 0)
if err != nil {
panic(err)
}
io.Copy(ioutil.Discard, fd)
这里返回了一个 File
类型,不难想象这个是基于文件 fd 的一层封装。这个里面大概做了什么?
syscall.Open
拿到文件 fd ,顺便设置了下垃圾回收时候的析构函数,其他的好像没了。远比网络 fd 要简单;Go 这个把标准输入、标准输出、标准错误输出 抽象成了读写源,对应了 os.Stdin
,os.Stdout
,os.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)
}
把标准输入作为读端,标准输出作为写端。编译出来运行吧,在终端随便输入一个字符串,敲下回车看下效果吧。
Reader/Writer 可以是缓冲 IO 的数据流。
在 c 语言,有人肯定用过 fopen
打开的文件(所谓的标准IO):
FILE * fopen ( const char * filename, const char * mode );
这个函数 open
出的是一个 FILE
结构体,而非之前常说的整数 fd 句柄。通过这个文件句柄的 IO 就是标准库帮你实现的缓冲 IO。c 语言的缓冲 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 的目的。
bufio 的使用接口:
// 创建一个带 buffer 的 writer,使用默认 buffer size 4096
bufio.NewWriter
// 创建一个带 buffer 的 writer 可以手动指定 buffer size
bufio.NewWriterSize
io/ioutil
里面实现的挺杂的,但真香;Reader
,Writer
,实现在 bytes 库中,bytes.NewReader
,bytes.NewBuffer
(换句话说,内存结构体和可以作为 Reader
,因为结构体可以强转成字节数组);Reader
,实现在 strings 库中,strings.NewReader
;Reader
,Writer
,实现在 net 库中,net.Conn
;Reader
,Writer
,实现在 os 库中,os.File
;本文意在构建一个 Go IO 知识框架,讲了 Go 标准库的 io 知识,以及实现的丰富多彩的 io 主体。得益于 Go 提供的 interface 机制,我们也可以定制各种各样的 Reader
,Writer
,你实现过什么样奇葩的 io 呢?
~完~
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/JniBMBw__WLbYtigj3eiXQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。