2015/12/12

Recent entries from same category

  1. golang オフィシャル謹製のパッケージ依存解決ツール「dep」
  2. Re: Go でシングルバイナリな Web アプリを開発しているときに webpack --watch をうまいところやる
  3. golang の http.Client を速くする
  4. Windows からも ssh でリモートコマンド実行したい、それ golang で出来るよ
  5. Matz の「言語のしくみ」を読んだ。

golang の defer は後処理のキューの登録です。コードを見ていないので分かりませんが、おそらくこういうコードを書いたのだと推測します。

package main

import (
    "fmt"
)

type foo struct {
    n int
}

func Create(n int) *foo {
    fmt.Printf("%v を作成\n", n)
    return &foo{n}
}

func Delete(f *foo) {
    fmt.Printf("%v を削除\n", f.n)
}

func main() {
    fmt.Println("開始")
    for i := 1; i <= 10; i++ {
        f := Create(i)
        defer Delete(f)
    }
    fmt.Println("終了")
}

この処理、実際には作成者の意図に反して以下の様に動作します。

開始
1 を作成
2 を作成
3 を作成
4 を作成
5 を作成
6 を作成
7 を作成
8 を作成
9 を作成
10 を作成
終了
10 を削除
9 を削除
8 を削除
7 を削除
6 を削除
5 を削除
4 を削除
3 を削除
2 を削除
1 を削除

つまり後処理を LIFO に登録し、関数スコープを抜けたタイミングで最後に登録したキューから取り出して実行します。ですので大量のループを実行するとキューが爆発します。さらに defer はその瞬間の変数をキャプチャします。

package main

import (
    "os"
)

func doSomething() {
    f, err := os.Open("test1.dat")
    if err != nil {
        return
    }
    defer f.Close() // test1.dat の Close()

    f, err = os.Open("test2.dat")
    if err != nil {
        return
    }
    defer f.Close() // test2.dat の Close()

    f, err = os.Open("test3.dat")
    if err != nil {
        return
    }
    defer f.Close() // test3.dat の Close()
}

func main() {
    doSomething()
}

つまり test1.dat の f も test2.dat の f も test3.dat の f もキューに乗っかります。それを確認する為にこのコードを以下の様に修正します。

package main

import (
    "fmt"
    "os"
)

func closeFile(f *os.File) {
    fmt.Printf("%v を Close() します\n", f.Name())
    f.Close()
}

func doSomething() {
    f, err := os.Open("test1.dat")
    if err != nil {
        return
    }
    defer closeFile(f) // test1.dat の Close()

    f, err = os.Open("test2.dat")
    if err != nil {
        return
    }
    defer closeFile(f) // test2.dat の Close()

    f, err = os.Open("test3.dat")
    if err != nil {
        return
    }
    defer closeFile(f) // test3.dat の Close()
}

func main() {
    doSomething()
}

実行すると以下の様に出力されます(ファイルは存在しているものとします)。

test3.dat を Close() します
test2.dat を Close() します
test1.dat を Close() します

ですのでループの中で defer を使ってはいけません。ただしループの中で処理するステートメントが多く、defer を使って簡素化したい場合は、以下の様に関数スコープを作ってあげる必要があります。

package main

import (
    "fmt"
)

type foo struct {
    n int
}

func Create(n int) *foo {
    fmt.Printf("%v を作成\n", n)
    return &foo{n}
}

func Delete(f *foo) {
    fmt.Printf("%v を削除\n", f.n)
}

func main() {
    fmt.Println("開始")
    for i := 1; i <= 10; i++ {
        func() {
            f := Create(i)
            defer Delete(f)
            // ... 色んな処理
        }()
    }
    fmt.Println("終了")
}

もちろんこの場合、途中で return しても大域脱出にならないので注意が必要です。


blog comments powered by Disqus