成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

你有考慮過Defer Close() 的風險嗎

開發 前端
作為一名 Gopher,我們很容易形成一個編程慣例:每當有一個實現了 io.Closer 接口的對象 x 時,在得到對象并檢查錯誤之后,會立即使用 defer x.Close() 以保證函數返回時 x 對象的關閉 。以下給出兩個慣用寫法例子。 HTTP 請求

[[410667]]

本文轉載自微信公眾號「Golang技術分享」,作者機器鈴砍菜刀 。轉載本文請聯系Golang技術分享公眾號。

作為一名 Gopher,我們很容易形成一個編程慣例:每當有一個實現了 io.Closer 接口的對象 x 時,在得到對象并檢查錯誤之后,會立即使用 defer x.Close() 以保證函數返回時 x 對象的關閉 。以下給出兩個慣用寫法例子。

HTTP 請求

  1. 1resp, err := http.Get("https://golang.google.cn/"
  2. 2if err != nil { 
  3. 3    return err 
  4. 4} 
  5. 5defer resp.Body.Close() 
  6. 6// The following code: handle resp 

訪問文件

  1. 1f, err := os.Open("/home/golangshare/gopher.txt"
  2. 2if err != nil { 
  3. 3    return err 
  4. 4} 
  5. 5defer f.Close() 
  6. 6// The following code: handle f 

存在問題

實際上,這種寫法是存在潛在問題的。defer x.Close() 會忽略它的返回值,但在執行 x.Close() 時,我們并不能保證 x 一定能正常關閉,萬一它返回錯誤應該怎么辦?這種寫法,會讓程序有可能出現非常難以排查的錯誤。

那么,Close() 方法會返回什么錯誤呢?在 POSIX 操作系統中,例如 Linux 或者 maxOS,關閉文件的 Close() 函數最終是調用了系統方法 close(),我們可以通過 man close 手冊,查看 close() 可能會返回什么錯誤

  1. 1ERRORS 
  2. 2     The close() system call will fail if: 
  3. 4     [EBADF]            fildes is not a valid, active file descriptor. 
  4. 6     [EINTR]            Its execution was interrupted by a signal. 
  5. 8     [EIO]              A previously-uncommitted write(2) encountered an 
  6. 9                        input/output error. 

錯誤 EBADF 表示無效文件描述符 fd,與本文中的情況無關;EINTR 是指的 Unix 信號打斷;那么本文中可能存在的錯誤是 EIO。

EIO 的錯誤是指未提交讀,這是什么錯誤呢?

計算機存儲層次結構

EIO 錯誤是指文件的 write() 的讀還未提交時就調用了 close() 方法。

上圖是一個經典的計算機存儲器層級結構,在這個層次結構中,從上至下,設備的訪問速度越來越慢,容量越來越大。存儲器層級結構的主要思想是上一層的存儲器作為低一層存儲器的高速緩存。

CPU 訪問寄存器會非常之快,相比之下,訪問 RAM 就會很慢,而訪問磁盤或者網絡,那意味著就是蹉跎光陰。如果每個 write() 調用都將數據同步地提交到磁盤,那么系統的整體性能將會極度降低,而我們的計算機是不會這樣工作的。當我們調用 write() 時,數據并沒有立即被寫到目標載體上,計算機存儲器每層載體都在緩存數據,在合適的時機下,將數據刷到下一層載體,這將寫入調用的同步、緩慢、阻塞的同步轉為了快速、異步的過程。

這樣看來,EIO 錯誤的確是我們需要提防的錯誤。這意味著如果我們嘗試將數據保存到磁盤,在 defer x.Close() 執行時,操作系統還并未將數據刷到磁盤,這時我們應該獲取到該錯誤提示(只要數據還未落盤,那數據就沒有持久化成功,它就是有可能丟失的,例如出現停電事故,這部分數據就永久消失了,且我們會毫不知情)。但是按照上文的慣例寫法,我們程序得到的是 nil 錯誤。

解決方案

我們針對關閉文件的情況,來探討幾種可行性改造方案

  • 第一種方案,那就是不使用 defer
  1.  1func solution01() error { 
  2.  2    f, err := os.Create("/home/golangshare/gopher.txt"
  3.  3    if err != nil { 
  4.  4        return err 
  5.  5    } 
  6.  6 
  7.  7    if _, err = io.WriteString(f, "hello gopher"); err != nil { 
  8.  8        f.Close() 
  9.  9        return err 
  10. 10    } 
  11. 11 
  12. 12    return f.Close() 
  13. 13} 

這種寫法就需要我們在 io.WriteString 執行失敗時,明確調用 f.Close() 進行關閉。但是這種方案,需要在每個發生錯誤的地方都要加上關閉語句 f.Close(),如果對 f 的寫操作 case 較多,容易存在遺漏關閉文件的風險。

  • 第二種方案是,通過命名返回值 err 和閉包來處理
  1.  1func solution02() (err error) { 
  2.  2    f, err := os.Create("/home/golangshare/gopher.txt"
  3.  3    if err != nil { 
  4.  4        return 
  5.  5    } 
  6.  6 
  7.  7    defer func() { 
  8.  8        closeErr := f.Close() 
  9.  9        if err == nil { 
  10. 10            err = closeErr 
  11. 11        } 
  12. 12    }() 
  13. 13 
  14. 14    _, err = io.WriteString(f, "hello gopher"
  15. 15    return 
  16. 16} 

這種方案解決了方案一中忘記關閉文件的風險,如果有更多 if err !=nil 的條件分支,這種模式可以有效降低代碼行數。

  • 第三種方案是,在函數最后 return 語句之前,顯示調用一次 f.Close()
  1.  1func solution03() error { 
  2.  2    f, err := os.Create("/home/golangshare/gopher.txt"
  3.  3    if err != nil { 
  4.  4        return err 
  5.  5    } 
  6.  6    defer f.Close() 
  7.  7 
  8.  8    if _, err := io.WriteString(f, "hello gopher"); err != nil { 
  9.  9        return err 
  10. 10    } 
  11. 11 
  12. 12    if err := f.Close(); err != nil { 
  13. 13        return err 
  14. 14    } 
  15. 15    return nil 
  16. 16} 

這種解決方案能在 io.WriteString 發生錯誤時,由于 defer f.Close() 的存在能得到 close 調用。也能在 io.WriteString 未發生錯誤,但緩存未刷新到磁盤時,得到 err := f.Close() 的錯誤,而且由于 defer f.Close() 并不會返回錯誤,所以并不擔心兩次 Close() 調用會將錯誤覆蓋。

  • 最后一種方案是,函數 return 時執行 f.Sync()
  1.  1func solution04() error { 
  2.  2    f, err := os.Create("/home/golangshare/gopher.txt"
  3.  3    if err != nil { 
  4.  4        return err 
  5.  5    } 
  6.  6    defer f.Close() 
  7.  7 
  8.  8    if _, err = io.WriteString(f, "hello world"); err != nil { 
  9.  9        return err 
  10. 10    } 
  11. 11 
  12. 12    return f.Sync() 
  13. 13} 

由于調用 close() 是最后一次獲取操作系統返回錯誤的機會,但是在我們關閉文件時,緩存不一定被會刷到磁盤上。那么,我們可以調用 f.Sync() (其內部調用系統函數 fsync )強制性讓內核將緩存持久到磁盤上去。

  1.  1// Sync commits the current contents of the file to stable storage. 
  2.  2// Typically, this means flushing the file system's in-memory copy 
  3.  3// of recently written data to disk. 
  4.  4func (f *File) Sync() error { 
  5.  5    if err := f.checkValid("sync"); err != nil { 
  6.  6        return err 
  7.  7    } 
  8.  8    if e := f.pfd.Fsync(); e != nil { 
  9.  9        return f.wrapErr("sync", e) 
  10. 10    } 
  11. 11    return nil 
  12. 12} 

 

由于 fsync 的調用,這種模式能很好地避免 close 出現的 EIO??梢灶A見的是,由于強制性刷盤,這種方案雖然能很好地保證數據安全性,但是在執行效率上卻會大打折扣。

 

責任編輯:武曉燕 來源: Golang技術分享
相關推薦

2023-10-04 17:25:01

面向接口編程

2020-07-14 07:48:19

Java對象JVM

2022-01-05 12:03:48

MySQL索引數據

2024-04-24 13:59:02

云原生應用

2016-04-19 16:01:05

2015-10-26 09:52:26

bat裁員信息安全

2020-11-04 17:35:39

網絡安全漏洞技術

2015-01-14 09:33:07

2020-06-20 14:09:01

信息安全數據技術

2015-12-15 10:46:57

云計算風險行業安全

2021-10-18 21:41:10

Go程序員 Defer

2019-08-14 05:35:08

2012-11-22 11:35:15

打印機

2018-02-25 22:37:21

應用開關Java

2010-12-21 11:31:09

2022-06-15 15:23:03

實用型代幣加密貨幣以太坊

2022-04-28 08:12:29

函數調用進程切換代碼

2023-03-13 13:36:00

Go擴容切片

2017-11-27 06:30:25

IP耦合架構

2019-04-11 18:46:22

APP手機應用下載
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 中文字幕亚洲一区 | 中文字幕在线精品 | 日韩精品一区二区三区视频播放 | 成人久久18免费网站麻豆 | 精品国产欧美一区二区三区成人 | 欧美一级黄色网 | 国产精品黄色 | 色欧美片视频在线观看 | 欧美一区二区 | 精品亚洲一区二区三区四区五区 | 成年视频在线观看福利资源 | 一区二区三区四区在线视频 | 欧美成年人 | 国产高清久久久 | 亚洲欧美日韩在线 | 久久久精品视频免费看 | 中文字幕一区在线 | 精品国产乱码久久久久久影片 | 在线观看国产 | 亚洲最大的黄色网址 | 久久99一区二区 | 欧美一级在线 | 国产成人在线播放 | 国产成人福利在线观看 | 久久一二 | 一区二区三区久久 | 久久国产精品一区二区三区 | www.五月天婷婷 | 精品久久九 | 亚洲第一女人av | 一级毛片视频 | 亚洲一区二区中文字幕在线观看 | 国产免费观看一级国产 | 国产亚洲精品美女久久久久久久久久 | 成人免费观看视频 | 亚洲一级黄色 | 欧美一级www片免费观看 | 一区二区av | 色婷婷精品国产一区二区三区 | 在线黄色影院 | 亚洲电影一级片 |