質問: ファイルを保存してそのファイル名をチャンネルを使って返すようなプログラムを書いています。 以下のような関数です。 ``` 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() //ファイル書き込み処理 ``` 確実にファイルを閉じたりロックをアンロックしたりするようにはできますか。
回答: defer ステートメントは、return したとき、または、プログラム/メソッドを終了したとき、panic が発生したときに実行されます。 公式のサイトより抜粋 > > A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking. > > > 質問を読ませていただいて気になったのですが、Download() の呼び出し元で os.Exit() を実行していたりしないでしょうか? os.Exit() が実行されると defer を実行せずにプログラムが終了してしまうので、そのケースのことをご質問されたとのかと思いました。 以下に defer が実行されない場合のサンプルコードを書きます。 ``` package main import ( "fmt" "os" ) func helloWorld(msg string, c chan string) { defer fmt.Println("helloWorld - End") fmt.Println("helloWorld - Start") c <- msg } func main() { c := make(chan string) fmt.Println("main - start") defer fmt.Println("main - End") go helloWorld("program sample", c) fmt.Println(<-c) os.Exit(0) } ``` 実行結果は、このようになります。 ``` main - start helloWorld - Start helloWorld - End program sample ```