goroutineでファイルを保存してファイル名を返す。
ファイルを保存してそのファイル名をチャンネルを使って返すようなプログラムを書いています。
以下のような関数です。
func Download(url string, fnameCh chan string, m *sync.Mutex)
呼び出し側で
var m sync.Mutex
go Download(url, fnameCh, &m)
fname := <- fnameCh
のように呼び出します。
urlからデータを取得してそれをファイルに保存します。
ファイル名が重複する可能性があるので保存時にMutexを使ってロックしています。
保存が終わったらfnameChを通してファイル名を返します。
ファイル保存処理の概要は下記のとおりです。
fname := [ファイル名]
// ロック獲得
m.Lock()
// 2番めに呼び出される
defer m.Unlock()
file, err := os.Create(fname)
if err != nil {
// エラーの場合""を送る
fnameCh <- ""
return
}
//1番目に呼び出される
defer file.Close()
// ファイル書き込み処理
// ファイル書き込み
// 呼び出し元はこのチャンネルをまっているので、ここで送るとdefer前にプログラムが終わる可能性が高い
fnameCh <- fname
これだとdefer
で指定した関数が呼ばれる前にmain関数が終わった場合defer
で指定した関数が呼ばれずにプログラムが終わってしまいます。つまりファイルクローズやロックの開放がされずに終わってしまいます。
そこで
func sendCh(ch chan string, fname string){
fnameCh <- [ファイル名]
}
という関数を作って、チャンネルへの入力をdefer
で指定すれば良いのですが、defer
は最後に宣言された関数から呼び出されるので必然的にファイル名を返すdefer
はファイルをクローズするdefer
やロックを開放するdefer
よりも前に宣言する必要があります。更に引数はdefer
宣言時のものに固定されてしまうので、ファイル書き込み時にエラーが出てしまった場合、エラーでファイル保存に失敗しているのにファイル名が返される状況になってしまいます。
fname := [ファイル名]
// 3番目に呼び出される。
// ここで呼び出すとfnameが[ファイル名]に決定するので
defer sendFname(fnameCh, fname)
m.Lock()
// 2番めに呼び出される。
defer m.Unlock()
file, err := os.Create(fname)
if err != nil {
// ここでのエラーを知らせられない
// fnameCh <- ""
return
}
//1番目に呼び出される
defer file.Close()
//ファイル書き込み処理
確実にファイルを閉じたりロックをアンロックしたりするようにはできますか。