22468


ファイルを保存してそのファイル名をチャンネルを使って返すようなプログラムを書いています。  
以下のような関数です。

[code]

    func Download(url string, fnameCh chan string, m *sync.Mutex)
    
[/code]

呼び出し側で

[code]

    var m sync.Mutex
    go Download(url, fnameCh, &m)
    fname := <- fnameCh
    
[/code]

のように呼び出します。  
urlからデータを取得してそれをファイルに保存します。  
ファイル名が重複する可能性があるので保存時にMutexを使ってロックしています。  
保存が終わったらfnameChを通してファイル名を返します。

ファイル保存処理の概要は下記のとおりです。

[code]

    fname := [ファイル名]
    // ロック獲得
    m.Lock()
    // 2番めに呼び出される
    defer m.Unlock()
    file, err := os.Create(fname)
    if err != nil {
        // エラーの場合""を送る
        fnameCh <- ""
        return
    }
    //1番目に呼び出される
    defer file.Close()
    
    // ファイル書き込み処理
    
    // ファイル書き込み
    // 呼び出し元はこのチャンネルをまっているので、ここで送るとdefer前にプログラムが終わる可能性が高い
    fnameCh <- fname
    
[/code]

これだと`defer`で指定した関数が呼ばれる前にmain関数が終わった場合`defer`で指定した関数が呼ばれずにプログラムが終わってしまいます。つまりファイルクローズやロックの開放がされずに終わってしまいます。

そこで

[code]

    func sendCh(ch chan string, fname string){
        fnameCh <- [ファイル名]
    }
    
[/code]

という関数を作って、チャンネルへの入力を`defer`で指定すれば良いのですが、`defer`は最後に宣言された関数から呼び出されるので必然的にファイル名を返す`defer`はファイルをクローズする`defer`やロックを開放する`defer`よりも前に宣言する必要があります。更に引数は`defer`宣言時のものに[固定されてしまう](http://qiita.com/tenntenn/items/9aebce917760f157c44e)ので、ファイル書き込み時にエラーが出てしまった場合、エラーでファイル保存に失敗しているのにファイル名が返される状況になってしまいます。

[code]

    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()
    
    //ファイル書き込み処理
    
[/code]

確実にファイルを閉じたりロックをアンロックしたりするようにはできますか。


CC BY-SA 3.0
2016-02-24T03:40:59.023
22464
2016-02-24T06:18:51.963
7232
question
go goroutine
goroutineでファイルを保存してファイル名を返す。


多分自己解決しました。

ファイルのオープンを行う関数を別で定義してそこでロック、ファイル開閉を行い、その関数内で`defer`するようにしました。  
そうすることでその関数から戻るときにロック解除とファイルのクローズが行われるので確実にチャンネルへの入力が最後になると思います。


CC BY-SA 3.0
2016-02-24T06:11:29.070
22468
2016-02-24T06:11:29.070
7232
22464
answer


defer ステートメントは、return したとき、または、プログラム／メソッドを終了したとき、panic が発生したときに実行されます。

[公式のサイトより抜粋](https://golang.org/ref/spec#Defer_statements)

> 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 が実行されない場合のサンプルコードを書きます。

[code]

    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)
    }
    
[/code]

実行結果は、このようになります。

[code]

    main - start
    helloWorld - Start
    helloWorld - End
    program sample
    
[/code]


CC BY-SA 3.0
2016-02-24T06:18:51.963
22470
2016-02-24T06:18:51.963
7266
22464
answer