前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >由一个问题引发对文件描述符的研究

由一个问题引发对文件描述符的研究

作者头像
暮雨
发布2019-08-21 10:25:28
6800
发布2019-08-21 10:25:28
举报
文章被收录于专栏:云端漫步

故事的起因

一次一个同事给我发了一段简单的代码,问我这段代码有什么问题?

代码语言:javascript
复制
package main

import (
    "fmt"
    "os"
)

func main() {
    f, err := os.Open("/test.txt")
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(f.Name(), "opened successfully")
}

看到这段代码后不加思索的回答,文件没有close,他说错,可能当时我们没在一个频道上,“err处理没有return”。

又仔细的看了下代码,发现err的处理代码块后使用了f.Name(),这个是存在问题的,因为当open发生错误时,返回的文件句柄则为nil,下文直接使用f.Name()。这种错误对于初学者经常会犯,改进的方式也很多,只要保证运行f.Name()的得到的f不为nil即可。可以在发生错误时,可以return或者os.Exit(-1) 也或下文的f.Name()放到else逻辑块中。

具体的处理方式要根据对报错的容忍度来处理

故事的发展

猜想

刚又提到,程序未对打开的文件做close,当然运行也没问题。既然没问题,也就没有close的必要。但是在open后加defer close已经成为go语言教课书级的示例。猜想,这里的open底层是一个I/O操作,在linux下所有的I/O操作都会转化为对文件的操作。如果程序对文件open后,没有关闭,则会一直占有资源,打开的数量越来越多,最终一定会因达到上限而导致程序出现问题。

调查

通过谷歌找到lsof这一命令可以查看打开的文件描述符的上限。

通过改命令发现我电脑上可以支持程序最大打开的文件描述符是4864个

验证

修改下代码,看下当程序打开4865次会发生什么情况?

代码语言:javascript
复制
package main

import (
    "fmt"
    "os"
)

func main() {
    for i := 1; i <= 4865; i++ {
        f, err := os.Open("./test.txt")
        if err != nil {
            fmt.Println(err)
        }
        fmt.Println(f.Name(), "opened successfully", i)
    }
    fmt.Scanln()
}

执行结果

发生了猜想中的问题,刚查看最大文件描述符是4864,这里只打开了4861个,为什么少了三个?

再次猜想

这里少了三个,那么这三个应该是被系统占用了,这里存在两种可能:

  1. 被其它程序占用
  2. 被该程序占用

再次验证

先来确认第一点,被其它程序占用 怎么验证呢?可以同样的程序,一个循环数设置3000,一个设置2000,如果结论成立的话,那么后运行的一个一定会出错。

程序并没有想象中的那样出错

那么就是该程序默认占用了三个 通过lsof查下进程打开的描述符情况

发现程序会默认打开三个系统文件描述符 也就是标准输入,标准输出,错误输出

这样的解释就可以自说其圆了,真的是这样么?

理论支撑

以下是维基百科对文件描述符的叙述

对文件的描述符的探索,可以画上一个句号了

遗留问题

在查看进程关联的文件时,发现有多出以上四个,这些有什么?这个问题作为一个遗留问题抛在这里,等待有心去探索

总结

通过以上的试验和验证,在程序打开文件后,记得close 完善后的最终处理代码

代码语言:javascript
复制
package main

import (
    "fmt"
    "os"
)

func main() {
    for i := 1; i <= 50000; i++ {
        f, err := os.Open("./test.txt")
        if err != nil {
            fmt.Println(err)
            return
        }
        fmt.Println(f.Name(), "opened successfully", i)
        f.Close()
    }
    fmt.Scanln()
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云端漫记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 故事的起因
  • 故事的发展
  • 遗留问题
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档