ludwig125のブログ

頑張りすぎずに頑張る父

go言語reflectメモ

reflect パッケージについてメモ

他記事:

ludwig125.hatenablog.com

reflectとは

  • reflectはGoでReflection機能を提供するためのパッケージ
  • このパッケージの関数を使うことで、引数の型や値を動的に取得できる

使い方1. (reflectで参照した変数の値を変更しないとき)

package main

import (
    "fmt"
    "reflect"
)

type Point struct {
    X int
    Y int
}

func main() {
    p := Point{10, 5} // 変数を定義
    rv := reflect.ValueOf(p)
    fmt.Printf("rv.Type = %v\n", rv.Type())  // 名前空間付きの型名
    fmt.Printf("rv.Kind= %v\n", rv.Kind()) // 格納リソース種別
    fmt.Printf("rv.Interface= %v\n", rv.Interface()) // interface{}としての実際の値

    xv := rv.Field(0) // rv内のX要素を取り出し
    fmt.Printf("xv: %d\n", xv.Int()) // intに変換して表示
}

実行結果

rv.Type = main.Point
rv.Kind= struct
rv.Interface= {10 5}
xv: 10

使い方2. (reflectで参照した変数の値を変更するとき)

  • 変数の値を変更するときは、SetInt(SetFloat, SetStringなど他にもいろいろ)を使う
  • ただし、変更するためには以下のようにValueOfにポインタを渡して、そのElemを取る必要がある
  • Elemはinterfaceの値もしくはポインタの参照先を返す

package main

import (
    "fmt"
    "reflect"
)

type Point struct {
    X int
    Y int
}

func main() {
    p := &Point{X: 10, Y: 5} // ポインタにすることによって編集可能にする
    rv := reflect.ValueOf(p).Elem() // Elemによってポインタが指している先の値を取得する
    fmt.Printf("rv.Type = %v\n", rv.Type())  // 名前空間付きの型名
    fmt.Printf("rv.Kind= %v\n", rv.Kind()) // 格納リソース種別
    fmt.Printf("rv.Interface= %v\n", rv.Interface()) // interface{}としての実際の値
    
    xv := rv.Field(0) // rv内のX要素を取り出し
    fmt.Printf("xv: %d\n", xv.Int()) // intに変換して表示    
    xv.SetInt(100) // intの100をX要素にセット
    fmt.Printf("after SetInt xv: %d\n", xv.Int()) // intに変換して表示
}

The Go Playground

実行結果

rv.Type = main.Point
rv.Kind= struct
rv.Interface= {10 5}
xv: 10
after SetInt xv: 100

【値を変更しようとしてpanicになる場合1】ポインタを渡さなかった場合

  • ポインタではなく値を変更しようとするとpanicになる
func main() {
    p := Point{X: 10, Y: 5}
    rv := reflect.ValueOf(p)
    xv := rv.Field(0)                             // rv内のX要素を取り出し
    fmt.Printf("xv: %d\n", xv.Int())              // intに変換して表示
    xv.SetInt(100)                               // intの100をX要素にセット => panic: reflect: reflect.Value.SetInt using unaddressable value
    fmt.Printf("after SetInt xv: %d\n", xv.Int()) // intに変換して表示
}

The Go Playground

実行結果

xv: 10
panic: reflect: reflect.Value.SetInt using unaddressable value

goroutine 1 [running]:
reflect.flag.mustBeAssignable(0x82, 0xf0a20)
    /usr/local/go/src/reflect/value.go:234 +0x180
reflect.Value.SetInt(0xf0460, 0x414020, 0x82, 0x7, 0x64, 0x0)
    /usr/local/go/src/reflect/value.go:1542 +0x40
main.main()
    /tmp/sandbox268672126/prog.go:18 +0x200

【値を変更しようとしてpanicになる場合2】Elemを使わなかった場合

  • Elemでreflect.Valueの指し示す先の値を取得していないとFieldメソッドを使った時にpanicになる
  • Field (再掲)
func main() {
    p := &Point{X: 10, Y: 5}
    rv := reflect.ValueOf(p)
    xv := rv.Field(0)                             // rv内のX要素を取り出し => panic: reflect: call of reflect.Value.Field on ptr Value
    fmt.Printf("xv: %d\n", xv.Int())              // intに変換して表示
    xv.SetInt(100)                                // intの100をX要素にセット
    fmt.Printf("after SetInt xv: %d\n", xv.Int()) // intに変換して表示
}

The Go Playground

実行結果

panic: reflect: call of reflect.Value.Field on ptr Value

goroutine 1 [running]:
reflect.Value.Field(0xe83c0, 0x414020, 0x16, 0x0, 0x40c120, 0x0, 0x90b80, 0x38f1)
    /usr/local/go/src/reflect/value.go:813 +0x160
main.main()
    /tmp/sandbox389676022/prog.go:16 +0xe0

値を変更できるか(SetXXXを使えるか)はCanSetを使うと判定できる

package main

import (
    "fmt"
    "reflect"
)

type Point struct {
    X int
    Y int
}

func main() {
    p := Point{X: 10, Y: 5}
    rv := reflect.ValueOf(p)
    if rv.CanSet() {
        fmt.Println("p CanSet: true")
        xv := rv.Field(0)                // rv内のX要素を取り出し
        fmt.Printf("xv: %d\n", xv.Int()) // intに変換して表示
        xv.SetInt(100)
        fmt.Printf("after SetInt xv: %d\n", xv.Int()) // intに変換して表示
    } else {
        fmt.Println("p CanSet: false")
    }
    pPtr := &Point{X: 10, Y: 5}
    rv = reflect.ValueOf(pPtr).Elem()
    if rv.CanSet() {
        fmt.Println("pPtr CanSet: true")
        xv := rv.Field(0)                // rv内のX要素を取り出し
        fmt.Printf("xv: %d\n", xv.Int()) // intに変換して表示
        xv.SetInt(100)
        fmt.Printf("after SetInt xv: %d\n", xv.Int()) // intに変換して表示
    } else {
        fmt.Println("pPtr CanSet: true")
    }
}

The Go Playground

実行結果

p CanSet: false
pPtr CanSet: true
xv: 10
after SetInt xv: 100

使えそうな使用例

1. 構造体のフィールドを順に出力

package main

import (
    "fmt"
    "reflect"
)

type Point struct {
    X int
    Y int
    Z int
}

func (p Point) PrintFiled() {
    v := reflect.ValueOf(p)
    t := v.Type()
    for i := 0; i < t.NumField(); i++ {
        // 構造体のフィールド名、フィールドの値、フィールドの型を出力
        fmt.Printf("%s %d %T\n", t.Field(i).Name, v.Field(i).Interface(), v.Field(i).Interface())
    }
}

func main() {
    p1 := Point{X: 10, Y: 5}
    p1.PrintFiled()

    fmt.Println("----")

    p2 := Point{X: 100, Y: 50, Z: 7}
    p2.PrintFiled()
}

The Go Playground

実行結果

X 10 int
Y 5 int
Z 0 int
----
X 100 int
Y 50 int
Z 7 int

2. 構造体の型が何であってもinterface型のSliceにまとめて返す

  • どんな型であってもinterfaceのスライスにしたい場面があったので
  • 以下のように、toInterfaceSlice関数を定義して、PointとUserのInterfaceSliceメソッドの中でこの関数を呼ぶだけにすることで共通化できた
  • toInterfaceSlice関数の引数はinterface{}なのでどの構造体でも受け付けられる
package main

import (
    "fmt"
    "reflect"
)

func toInterfaceSlice(v interface{}) []interface{} {
    var vs []interface{}
    rv := reflect.ValueOf(v)
    rt := rv.Type()
    for i := 0; i < rt.NumField(); i++ {
        vs = append(vs, rv.Field(i).Interface())
    }
    return vs
}

type Point struct {
    X int
    Y int
    Z int
}

func (p Point) InterfaceSlice() []interface{} {
    return toInterfaceSlice(p)
}

type User struct {
    ID     int
    Name   string
    Height float64
    Weight float64
}
func (u User) InterfaceSlice() []interface{} {
    return toInterfaceSlice(u)
}

func main() {
    p := Point{X: 10, Y: 5, Z: 1}
    for _, v := range p.InterfaceSlice() {
        fmt.Printf("%v %T\n", v, v)
    }

    fmt.Println("----")

    u := User{ID: 1001, Name: "hoge", Height: 170.1, Weight: 60.45}
    for _, v := range u.InterfaceSlice() {
        fmt.Printf("%v %T\n", v, v)
    }
}

The Go Playground

実行結果

10 int
5 int
1 int
----
1001 int
hoge string
170.1 float64
60.45 float64

2-2. 関数に渡すパラメータが構造体のポインタの場合でも対応できるようにする

  • 上のtoInterfaceSlice関数で引数が構造体のポインタの場合には以下のようにElemを使って実体を取得するようにすれば、 引数が構造体の場合も、構造体のポインタの場合も両方対応できる
func toInterfaceSlice(v interface{}) []interface{} {
    var vs []interface{}

    rv := reflect.ValueOf(v)

    fmt.Printf("rv.Kind %v\n", rv.Kind())
    // パラメータvが構造体のポインタのときはElemでポインタの指している先の値を取得する
    if rv.Kind() == reflect.Ptr {
        // vが構造体のポインタの時はここを通る
        rv = reflect.ValueOf(v).Elem()
    }
    rt := rv.Type()
    for i := 0; i < rt.NumField(); i++ {
        vs = append(vs, rv.Field(i).Interface())
    }
    return vs
}

type Point struct {
    X int
    Y int
    Z int
}

func (p Point) InterfaceSlice() []interface{} {
    // toInterfaceSliceには構造体を渡す
    return toInterfaceSlice(p)
}

type User struct {
    ID     int
    Name   string
    Height float64
    Weight float64
}

func (u *User) InterfaceSlice() []interface{} {
    // toInterfaceSliceには構造体のポインタを渡す
    return toInterfaceSlice(u)
}

全体のコードは以下

The Go Playground

実行結果

rv.Kind struct
10 int
5 int
1 int
----
rv.Kind ptr
1001 int
hoge string
170.1 float64
60.45 float64

2-3. 構造体のフィールドに構造体を含んでいる場合にも対応できるようにする

  • 以下のように、構造体のフィールドに構造体を持つ場合(ネストしている場合)にも対応できるようにしたい
type GamePlayerInfo struct {
    User
    Point
}
  • 以下のように条件分岐して再帰を使ってみる
func toInterfaceSlice(v interface{}) []interface{} {
    var vs []interface{}

    rv := reflect.ValueOf(v)
    if rv.Kind() == reflect.Ptr {
        rv = reflect.ValueOf(v).Elem()
    }
    rt := rv.Type()
    for i := 0; i < rt.NumField(); i++ {
        if rv.Field(i).Kind() == reflect.Struct {
            // フィールドがstructの場合は再帰でinterfaceのSliceを取得して後ろにつなげる
            sl := toInterfaceSlice(rv.Field(i).Interface())
            vs = append(vs, sl...)
        } else {
            vs = append(vs, rv.Field(i).Interface())
        }
    }
    return vs
}

全体のコードは以下

The Go Playground

実行結果

10 int
5 int
1 int
----
1001 int
hoge string
170.1 float64
60.45 float64
----
1001 int
hoge string
170.1 float64
60.45 float64
10 int
5 int
1 int

2-4. 構造体のフィールドで〇〇メソッドを持つ場合はそれを使うようにする(おまけ)

  • 以下のように、Attribute変数(Enum)のように、Stringメソッドを持つ場合はそれで出力させる
  • MethodByName を使う
type Attribute int

const (
    Fire Attribute = iota
    Water
    Earth
    Air
)

// constのString変換メソッド
func (a Attribute) String() string {
    return []string{"Fire", "Water", "Earth", "Air"}[a]
}

type SpecialGamePlayerInfo struct {
    User
    Point
    Attribute
}

The Go Playground

参考

Go 言語 reflect チートシート - Qiita

Go言語の型とreflect - Qiita

Go言語におけるinterface{}とリフレクションを使ったパターン - Qiita

reflect-examples/README.md at master · a8m/reflect-examples · GitHub

Go言語におけるinterface{}とリフレクションを使ったパターン - Qiita

Learning to Use Go Reflection – Capital One Tech – Medium

CloudSQLを安くするために考えたこと

無料トライアル期間が終わったのでCloudSQLのマイグレーションをした話

安さを追求したわけではなく、自分なりの妥協点を探っただけ

事象

2019/5/22 CloudSQLに接続できなくなった

$ gcloud sql connect myfinance --user=root
ERROR: (gcloud.sql.connect) HTTPError 409: The instance or operation is not in an appropriate state to handle the request.

GoogleAppEngineのログには以下が出力されていた

failed to insert table: daily, err: driver: bad connection, query: 

原因

Google Cloud PlatformのUIの一番上を見ると無料トライアル期間が終わったとを通知していた

無料トライアルは終了しました

上のUIの右の「アップグレード」ボタンを押すと以下のように表示された

アカウントのアップグレード

これを承諾するまえに、そもそも無料トライアル期間中はいくらくらい使っていたのか確認してみた

これからかかるはずの料金の確認

レポートを見てみる

レポートの見方 - 請求レポートによる費用傾向の表示  |  Cloud Billing のドキュメント  |  Google Cloud

「お支払い」の「レポート」を見てみた

image

レポートの、先月分をプロダクト単位で見てみる - グループ条件を変更

SKU単位で見てみると使用しているサービスの内訳がわかる

image

SKU プロダクト SKU ID 使用 クレジット適用前の費用 プロモーション 割引 クレジット適用後の費用
DB standard Intel N1 1 VCPU running in Japan (with 30% promotional discount) Cloud SQL 5356-0253-5FBD 720 hour ¥6,996 ¥-6,996 ¥0
Storage PD SSD for DB in Japan Cloud SQL 9D66-0506-E274 10 gibibyte month ¥244 ¥-244 ¥0

特にこれ - DB standard Intel N1 1 VCPU running in Japan (with 30% promotional discount) - クレジット適用前の費用:¥6,996

・・・なにこの値段

SKUって?

単品管理 - Wikipedia

SKUは最小管理単位 (Stock Keeping Unit) の略。

Google Cloud PlatformにはSKUの意味について説明がなかったのでたぶんこれのことだと思う

毎月の請求について  |  Cloud Billing のドキュメント  |  Google Cloud

SKU ID サービスが使用するリソースの ID。SKU の詳細な一覧については、GCP SKU をご覧ください。

GCP SKU一覧

SKUs  |  Google Cloud

この単位で料金を決めているらしい

この金額が正当なのか確認

上で表示したレポートのSKUの一覧を見ると、 DB standard Intel N1 1 VCPU running in Japan (with 30% promotional discount) に一番金がかかっているらしい

  • ¥6,996

image

この「DB standard Intel N1 1 VCPU running in Japan (with 30% promotional discount)」を上の公式の表「GCP SKU一覧」から探すとあった image

DB standard Intel N1 1 VCPU running in Japan (with 30% promotional discount) - 0.088 USD per hour

この0.088を4月中フル稼働した場合(720時間)で、現在の円ドルレート 約110円を当てはめると、 0.088720110=6969.6 となって上とほぼ等しい

with 30% promotional discount

この「with 30% promotional discount」というのは、 「継続利用割引」というサービスらしい

Google Compute Engine の料金  |  Compute Engine ドキュメント  |  Google Cloud

継続利用割引は、使用する vCPU とメモリ量ごとに計算されます。Compute Engine では、1 か月の 25% を超える期間にわたり vCPU またはメモリ量がインスタンスで使用されると、そのリソースの使用が 1 秒延びるごとに割引が自動的に適用されます。 使用時間が増えるほど割引率は高くなり、1 か月ずっと稼働するインスタンスでは vCPU とメモリの費用の最大 30% の正味割引を受けることができます。

CloudSQLの料金体系について確認する

Cloud SQL の料金  |  Cloud SQL ドキュメント  |  Google Cloud

自分が使っているのはCloudSQLのMysql 第 2 世代なのでそれを見る

MySQL 第 2 世代の料金 第 2 世代の料金は、次の料金で構成されます。 インスタンスの料金 ストレージの料金 ネットワークの料金

mysql 2ndの料金

東京(asia-northeast1)を見ると、

マシンタイプ 仮想 CPU 数 RAM(GB) 最大ストレージ容量 最大接続数 料金(米ドル) 継続利用価格(米ドル)
db-n1-standard-1 1 3.75 10,230 GB 4,000 $0.13 $0.09

となっていた。

image

これは上のDB standard Intel N1 1 VCPU running in Japan (with 30% promotional discount) の 0.088という数字と一致する

なるほど・・・高い

インスタンスの設定

インスタンスの設定  |  Cloud SQL ドキュメント  |  Google Cloud

Google Cloud SQL の第 1 世代および第 2 世代のインスタンスで使用できるすべての設定について

こちらに詳しく書いてある

どうして720時間(一月休みなく動かし続けるとこの時間)使っていることになっているのか?

これが一番疑問だった

自分のサービスはCronで一日せいぜい2時間くらいしか動いていないのにどうして休みなく稼働していることになっているのか?

調べてみると、以下の通り明示的にDBを停止させない限り、ずーっと稼働していて、その分料金もかかるらしい

Mysqlの第 2 世代の特徴

第 2 世代機能  |  Cloud SQL ドキュメント  |  Google Cloud

料金の違い Cloud SQL 第 2 世代では、従量制の料金パッケージが提供されていません。インスタンスの料金は、マシンタイプによって決まります。分単位の課金と継続利用割引の導入により、Cloud SQL 第 2 世代では多くのワークロードで費用対効果を高めることができます。詳しくは、料金のページをご覧ください。

アクティベーションポリシー

インスタンスの設定  |  Cloud SQL ドキュメント  |  Google Cloud

アクティベーション ポリシー 第 2 世代インスタンスの場合、アクティベーション ポリシーはインスタンスを起動または停止するためにのみ使用されます。アクティベーション ポリシーを [常にオン] に設定するとインスタンスが起動し、[オフ] に設定するとインスタンスが停止します。

つまり、オフにしない限り利用し続けている状態となるらしい

  • 第1世代にはON DEMAND というポリシーがあって、リクエストが来たら起動する(立ち上がりに時間かかるけど)という仕組みがあったらしいが、それは第2世代にはない

インスタンスの起動、停止はどうすればいいのか

インスタンスの起動、停止、再起動  |  Cloud SQL  |  Google Cloud

ブラウザまたはgcloudコマンドで起動・停止できるらしい

料金を安くできないか

以上より、料金を安く抑えてCloudSQLを使うためには以下の3つが考えられそう

  1. インスタンスのリージョンを安いところにする
  2. マシンを一番安いものにする
  3. 使わないときはインスタンスを止める

3番目については自動でgcloudコマンドを実行できるようにそのうちしたい

一番安いリージョンの一番安いマシンを調べる

https://cloud.google.com/sql/pricing?hl=ja

をいろいろ見た結果、 アメリカなどのリージョンのdb-f1-microマシンが一番安かったのでこれを選択 なんでも良かったので、アイオワにする

image

リージョン マシンタイプ 仮想 CPU 数 RAM(GB) 最大ストレージ容量 最大接続数 料金(米ドル) 継続利用価格(米ドル)
アイオワ db-f1-micro* 共有 0.6 3,062 GB 250 $0.0150 $0.0105
東京 db-n1-standard-1 1 3.75 10,230 GB 4,000 $0.1255 $0.0878

アイオワのdb-f1-micro は東京のdb-n1-standard-1の1/8以下

継続利用価格(米ドル)では$0.0105 / hour

※この料金は一時間あたりの利用料金のことらしい

Google Cloud Platform 料金計算ツール  |  Google Cloud Platform  |  Google Cloud - このツールで見積もりもできる

自分のサービスはSLAがめちゃめちゃ低く、レイテンシーはほとんど気にしない。データサイズも数十GBなので、 db-f1-microで十分そう

想定費用を計算してみた

リージョン×マシンタイプ 料金(24時間×31日×110円 ※ 110は現在のレート)
アイオワ db-f1-micro 0.01052431*110=859.32円
東京 db-n1-standard-1o 0.08782431*110=7185.552円

インスタンスの他にストレージとネットワークの料金が別にかかるが、一旦これだけ考える

もとの金額に比べれば、趣味で使う金額としてだいぶ現実的になった

ストレージについて

ストレージはSSDのほうが当然高い

でもこれくらいなら許容しようかな(あとで変えられるのでSSDを選ぶ) - $0.17 per GB/month for SSD storage capacity - $0.09 per GB/month for HDD storage capacity

SKUの表で料金を確認

https://cloud.google.com/skus/?currency=JPY&filter=micro image

CloudSQLの表と名前が一対一対応していないのでわかりにくいけど、おそらくこれが対応するSKUとその金額だと思える

DB generic Micro instance with burstable CPU running in Americas (with 30% promotional discount) - 0.0105 USD per hour

新規DBの作成

アップグレード

最初のアップグレードボタンを押して使えるようにする

インスタンスの作成

https://console.cloud.google.com/sql/instances から、アイオワリージョンのdb-f1-microを新しく作る

image

で「インスタンスを作成」

image

Mysqlを選択

image

任意のインスタンスIDとパスワードを設定する

このときに下の「設定オプションを表示」を押すと、マシンタイプなどを細かく設定できる - リージョンはあとから変えられない

※あとでも変更できるけど、何も設定しないとdb-n1-standard-1になってしまうので超注意!!

インスタンスの編集

上の、「設定オプションを表示」をいじらなくても、あとからマシンタイプなどは変えられる

Google Cloud PlatformSQLインスタンスが見られるので、その画面の中の「設定」→「設定を編集」から設定を変更できる

image

マシンタイプとストレージの設定

マシンタイプとストレージの設定を編集

変更前

マシンタイプとストレージ

変更後

image

  • マシンタイプ:db-f1-micro
  • ストレージの種類:SSD
  • ストレージ容量 :最低の10GB
  • ストレージの自動増量を有効化:チェックした

自動バックアップの有効化

しない - とりあえず、自分で定期的に頑張ることにする

image

新旧DBデータ移行汎用手順

0. 準備

Cloud SQL for MySQL の使用  |  Go の App Engine スタンダード環境  |  Google Cloud

Cloud SQL for MySQL のクイックスタート  |  Cloud SQL for MySQL  |  Google Cloud

これに従ってcloud_sql_proxyをローカルマシンに入れておく

事前に新旧DBのインスタンス接続名を調べておく

Google Cloud Platform の左上のボタンを押してからSQLを選んで、

image

対象のインスタンスIDを選択して、 「このインスタンスに接続」->「インスタンス接続名」から取得できる

このインスタンスに接続

1. 旧DBへのプロキシサーバ立ち上げ

ローカルの端末で以下を実施

$ cloud_sql_proxy -instances=<インスタンス接続名>=tcp:3307

2. 旧DBのテーブルをDump(Export)

別の端末を開いてTableデータをDump

$mysqldump -u root  -p --host 127.0.0.1 --port 3307 <Database名> <テーブル名> > dumpdata.sql
  • Database名: show databases; で出てくるなかの対象のDatabase

3. 新DBのテーブルへImport

新DBへのプロキシサーバ立ち上げ

先程までの旧DBに繋がっていたプロキシサーバは止めて、ローカルの端末で以下を実施

$ cloud_sql_proxy -instances=<新DBのインスタンス接続名>=tcp:3307

4. 新DBでDatabaseを作っておく

別の端末で新DBに接続

$mysql -u root -p --host 127.0.0.1 --port 3307


MySQL [(none)]> CREATE DATABASE <Database名>;

5. 新DBのテーブルへImport

別の端末を開いてTableデータをImport

$mysql -u root -p --host 127.0.0.1 --port 3307 <Database名> < dumpdata.sql

GoogleHomeまたはAndroidに話しかけてスプレッドシートにメモを取る

概要

GoogleHomeやAndroidに話しかけるだけでスプレッドシートにメモを取ることができる

ちょっとしたメモを取るのに便利

ludwig125.hatenablog.com

こちらとほぼ同じ方法で作った

手順

これだけ 1.スプレッドシートを用意する 2.スプレッドシートにGoogleAppScriptを追加する 3.IFTTTを登録する

1.スプレッドシートシートを用意する

f:id:ludwig125:20190513153022p:plain

新しいスプレッドシートを作成 → 空白を選ぶ

f:id:ludwig125:20190513153111p:plain

ここでは適当に「test」という名前のスプレッドシートにしたが何でもいい

2.スプレッドシートにGoogleAppScriptを追加する

GoogleAppScriptを作成

f:id:ludwig125:20190513153157p:plain

上のタブの「ツール」→ 「スクリプト エディタ」を選択

適当に「test-gas」というプロジェクト名にするが何でもいい

f:id:ludwig125:20190513153347p:plain

コード.gsに以下を記入する

function addDate() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var lastRow = sheet.getLastRow();
  var date = new Date();
  var unixTimestamp = Math.round( date.getTime() / 1000 );
  sheet.getRange(lastRow, 1).setValue(date);
}

「lastRow, 1」にすると縦一列目に入力時刻が記載される

f:id:ludwig125:20190513153517p:plain

トリガーを追加

f:id:ludwig125:20190513153539p:plain

コード.gsの上の部分の時計のアイコンを選択

f:id:ludwig125:20190513153614p:plain

「新しいトリガーを作成します。」をクリック

必要な部分を選択 - 実行する関数を選択: addDate - 実行するデプロイを選択:Head ←これしかなかった - イベントのソースを選択:スプレッドシートから - イベントの種類を選択:変更時 - エラー通知設定:1週間おきに通知を受け取る ← 好みで変えていい

f:id:ludwig125:20190513153933p:plain

記載したら保存

「アカウントの選択」でGmailIDを選択

f:id:ludwig125:20190513154037p:plain

「このアプリは確認されていません」と出るが、無視して詳細を選択して、 「~~(安全ではないページ)に移動」をクリックする

f:id:ludwig125:20190513154142p:plain

「test-gas が Google アカウントへのアクセスをリクエストしています」 の下の「許可」を押す

f:id:ludwig125:20190513154417p:plain

ここまでの動作確認

ここまでやったら最初のtestシートに戻る

f:id:ludwig125:20190513154332p:plain

Bより右の列に適当に入力してEnterキーを押すと以下のようにA列に日付が自動で出力されるはず

f:id:ludwig125:20190513154810p:plain

これだけでもいいが、時刻までシートに表示させたいので、表示形式を変える

f:id:ludwig125:20190513154914p:plain

A列目を全部選択して、「表示形式」→ 「数値」→「日時」を選択

f:id:ludwig125:20190513155100p:plain

またA列目を全部選択して右クリックから「条件付き書式」を選ぶと、以降A列目だけ色がつくので見やすい(やらなくてもいい)

3.IFTTTを登録する

IFTTTのトップページにいって「My Applets」を選択

ifttt.com

f:id:ludwig125:20190513162254p:plain

New Appletを選択

this

f:id:ludwig125:20190513162322p:plain

f:id:ludwig125:20190513162349p:plain

Google Assistantを選択

Say a phrase with a text ingredient を選択

f:id:ludwig125:20190513162542p:plain

「テスト $」や「test $」など発音しやすい(かつGoogleAssistantが他の言葉と区別しやすい)言葉を選択

Create triggerを押す

that

f:id:ludwig125:20190513162723p:plain

Google Sheetsを選択

Add row to spreadsheet を選択して以下のようにする

f:id:ludwig125:20190513162843p:plain

f:id:ludwig125:20190513162912p:plain

もしGoogleAppScriptおよびトリガーを設定せずに、左の列にCreatedAtを選択するとGoogleHome経由で入力すると「May 14, 2019 at 08:48PM」のような形式でスプレッドシートに記載される この機能は昔は機能していなかった気がするので、GoogleAppScriptをわざわざ作ったんだけど。。 ただ、日付の形式は変えられない。 もし形式がこれでよければスプレッドシート側のスクリプトは不要そう f:id:ludwig125:20190515064221p:plain

動作確認

GoogleHomeまたはAndroidで「OK Google テスト 123」

f:id:ludwig125:20190515065204p:plain

なぜかGoogleHome経由からだと時刻が表示されない。 でもセルを見れば時刻がわかるからいいかな。。

GoogleHome経由でスプレッドシートに記入されないとき

Google ドライブ - 1 か所であらゆるファイルを保管 Google Driveを見て、以下のthatで定義したGoogleHomeのフォルダに対象のシートが入っているか確認する

f:id:ludwig125:20190515065311p:plain

別のフォルダの同名のシートに書き込まれていることがあった

f:id:ludwig125:20190515063015p:plain

GoogleAppEngine(GAE)でgoroutineを使う

GoogleAppEngineでgoroutineを使う

以下に記載されている通り、GoogleAppEngineでは並列処理はサポートしていない。

Go Runtime Environment  |  App Engine standard environment for Go  |  Google Cloud

App Engine の Go ランタイム環境は、goroutine を完全にサポートしていますが、並列実行はサポートしていません。goroutine は単一のオペレーティング システム スレッドにスケジュールされます。このシングルスレッド制限は、今後のバージョンで解除される予定です。特定のインスタンスで複数のリクエストを同時に処理することができます。これは、1 つのリクエストで Cloud Datastore API の呼び出しを待っている間に、同じインスタンスで別のリクエストを処理できることを意味します。

しかし、並行処理はできる

これは、同時に複数の処理を実行すること(並列処理)はできないが、APIのレスポンスなどを待つ間に別の処理をすること(並行処理)はできるということを意味する

【脱線】並行性と並列性の違い

www.oreilly.co.jp

「Go言語による並行処理」ではこの違いを以下の一文で表している

並行性はコードの性質を指し、並列性は動作しているプログラムの性質を指します。

ピンと来ない表現・・・

検証

検証用のコード

以下のような3つのHandlerを作成して、それぞれの挙動を確認する - testHandler - testGoroutineHandler - testGoroutineHandler2

挙動の違いが分かりやすいように、time.Sleep(3 * time.Second)を入れている

このコードは以下の「複数のgoroutineの結果の取得」に書いたものを使っている go言語の並行処理 - ludwig125のブログ

package main                                                                                   

import (
    "fmt"
    "net/http"
    "sync"
    "time"

    "google.golang.org/appengine" // Required external App Engine library
    "google.golang.org/appengine/log"
    "google.golang.org/appengine/urlfetch" // 外部にhttpするため(http.GetはGAEでは使えない)
)

func main() {
    http.HandleFunc("/test", testHandler)
    http.HandleFunc("/test_goroutine", testGoroutineHandler)
    http.HandleFunc("/test_goroutine2", testGoroutineHandler2)
    appengine.Main() // Starts the server to receive requests
}

func testHandler(w http.ResponseWriter, r *http.Request) {
    // GAE log
    ctx := appengine.NewContext(r)
    client := urlfetch.Client(ctx)

    urls := []string{"https://www.google.com", "https://badhost", "https://www.yahoo.co.jp/"}
    checkStatus := func(urls ...string) []*http.Response {
        var response []*http.Response
        for _, url := range urls {
            log.Infof(ctx, "client.Get '%s'", url)
            resp, err := client.Get(url)
            if err != nil {
                log.Errorf(ctx, "%v", err)
                continue
            }
            response = append(response, resp)
            // 関数の実行にかかる時間を遅らせる
            log.Infof(ctx, "sleep 3 sec")
            time.Sleep(3 * time.Second)
        }
        return response
    }

    for _, response := range checkStatus(urls...) {
        log.Infof(ctx, "checkStatus: status: %s", response.Status)
    }                                                                                                                                                                 
}

func testGoroutineHandler(w http.ResponseWriter, r *http.Request) {
    // GAE log
    ctx := appengine.NewContext(r)
    client := urlfetch.Client(ctx)

    type Result struct {
        Error    error
        Response *http.Response
    }
    checkStatus := func(
        done <-chan interface{},
        urls ...string,
    ) <-chan Result {
        resultChan := make(chan Result)
        go func() {
            defer close(resultChan)
            for _, url := range urls {
                log.Infof(ctx, "client.Get '%s'", url)
                resp, err := client.Get(url)
                // 関数の実行にかかる時間を遅らせる
                log.Infof(ctx, "sleep 3 sec")
                time.Sleep(3 * time.Second)
                result := Result{Error: err, Response: resp}
                select {
                case <-done:
                    return
                case resultChan <- result:
                }
            }
        }()
        return resultChan
    }

    done := make(chan interface{})                                                                                             
    defer close(done)

    urls := []string{"https://www.google.com", "https://badhost", "https://www.yahoo.co.jp/"}
    for result := range checkStatus(done, urls...) {
        if result.Error != nil {
            log.Infof(ctx, "error: %v", result.Error)
            continue
        }
        log.Infof(ctx, "checkStatus: status: %s", result.Response.Status)
    }
}

    urls := []string{"https://www.google.com", "https://badhost", "https://www.yahoo.co.jp/"}
    for result := range checkStatus(done, urls...) {
        if result.Error != nil {
            log.Infof(ctx, "error: %v", result.Error)
            continue
        }
        //log.Infof(ctx, "%s", formatStatus(result.Response.Status))
        log.Infof(ctx, "checkStatus: status: %s", result.Response.Status)
    }
}

func testGoroutineHandler2(w http.ResponseWriter, r *http.Request) {
    // GAE log
    ctx := appengine.NewContext(r)
    client := urlfetch.Client(ctx)

    type Result struct {
        Error    error
        Response *http.Response
    }
    checkStatus := func(
        done <-chan interface{},
        urls []string,
    ) <-chan Result {
        resultChan := make(chan Result, len(urls))
        wg := new(sync.WaitGroup)

        defer close(resultChan)
        for _, url := range urls {
            wg.Add(1)
            go func(url string) {
                defer wg.Done()
                log.Infof(ctx, "client.Get '%s'", url)
                resp, err := client.Get(url)
                // 関数の実行にかかる時間を遅らせる
                log.Infof(ctx, "sleep 3 sec")
                time.Sleep(3 * time.Second)
                select {
                case <-done:
                    return
                case resultChan <- Result{Error: err, Response: resp}:
                }
            }(url)
        }
        wg.Wait()
        return resultChan                                                                                                             
    }

    done := make(chan interface{})
    defer close(done)
    urls := []string{"https://www.google.com", "https://badhost", "https://www.yahoo.co.jp/"}
    for result := range checkStatus(done, urls) {
        if result.Error != nil {
            log.Infof(ctx, "error: %v", result.Error)
            continue
        }
        log.Infof(ctx, "checkStatus: status: %s", result.Response.Status)
    }
}

実行して結果を確認

GoogleAppEngineの左側からトレースを選ぶと詳細がみられる

f:id:ludwig125:20190513065411p:plain

またトレース画面の右側のログの「表示」をクリックするとログがみられる

1.test(testHandler)

トレース f:id:ludwig125:20190513065517p:plain - 3つのURLの処理を3秒おきに実行していることがわかる

ログ f:id:ludwig125:20190513065641p:plain

2.test_goroutine(testGoroutineHandler)

  • goroutineを使っているが、URLは3つを順番に処理している
  • 3つそれぞれで3秒ごとのSleepを入れているため、全体の実行時間は9秒ちょっとになっている

トレース

f:id:ludwig125:20190513065853p:plain

ログ

f:id:ludwig125:20190513065945p:plain

3.test_goroutine2(testGoroutineHandler2)

  • (ほぼ)同時に3つのgoroutineを実行している
  • 「並列実行はサポートしていません」と公式に書いてあるので、おそらく全く同時ではないはずだが、これ見るとほぼ同時に見える。。
  • ともかく、並行で3つの処理を行うようにしたことで、全体としての処理時間が3秒ちょっとになった

トレース

f:id:ludwig125:20190513070119p:plain

ログ

f:id:ludwig125:20190513070141p:plain

相関係数について

相関係数について

下記相関係数の導出までの説明

正の相関と負の相関

  • データ列1のように、年月日に対して値が増加している場合、これを「正の相関がある」という
  • データ列2のように、年月日に対して値が増加している場合、これを「負の相関がある」という
年月日 20140101 20140102 20140103 20140104 20140105 20140106 20140107 20140108 20140109 20140110
データ列1 正の相関 101 102 103 104 105 106 107 108 109 110
データ列2 負の相関 110 109 108 107 106 105 104 103 102 101

01

相関を数値で表す

参考:http://mcn-www.jwu.ac.jp/~yokamoto/ccc/stat/p22covcor/

年月日を変数x、相関関係を考えたい数値を変数yとする
変数xの平均をm_x、変数yの平均をm_yとする
変数の数をNとする
  • Σ(x-m_x)(y-m_y)/N を計算すると、xとyに正の相関があるか、負の相関があるか分かる ⇒ 上記をxとyの 共分散 という
  • 例えばデータ列1の例では、年月日をx、データ列1の数値をyとすると下記のような計算になる
xの平均 = (20140101 + 20140102 + ・・・ + 20140110)/10 = 20140105.5
yの平均 = (101 + 102 + ・・・ + 110)/10 = 105.5

xとyの共分散 = Σ(x-m_x)(y-m_y)/N
             = { (20140101-20140105.5) × (101-105.5) + ・・・ +(20140110-20140105.5) × (110-105.5) }/10 = 8.25

データ列1とデータ列2の共分散を計算すると下記の通り(エクセルではCOVAR関数を使用)

テータ列 相関 共分散
データ列1 正の相関 8.25
データ列2 負の相関 -8.25

共分散で相関係数を考える場合の問題点

  • 共分散は変数が等倍されるだけで値が変わってしまうため、
  • 複数の単位が異なったデータ列について相関の大きさを比較するには向いていない
  • データ列1やデータ列2の数値を100倍したものをそれぞれデータ列3、データ列4とする
年月日 20140101 20140102 20140103 20140104 20140105 20140106 20140107 20140108 20140109 20140110
データ列3 正の相関(数値が100倍) 10100 10200 10300 10400 10500 10600 10700 10800 10900 11000
データ列4 負の相関(数値が100倍) 11000 10900 10800 10700 10600 10500 10400 10300 10200 10100

02

  • データ列3とデータ列4の共分散
テータ列 相関 共分散
データ列3 正の相関(数値が100倍) 825
データ列4 負の相関(数値が100倍) -825

⇒ 数値が100倍だと共分散も100倍になってしまう

共分散を相関の大きさだと考えた時の問題点として データ列1、2の数値の単位がメートルだとしたとき、 それをセンチメートル単位で見たものをデータ列3、4とすると、 同じ数字を見ているにもかかわらず、2つの場合で相関の大きさが変化するということになってしまう

相関係数

  • 共分散を規格化して、単位をによらない無次元の量としたものを相関係数といい、下記の通り定義する
相関係数 = Σ(x-m_x)(y-m_y)/N ÷ (√(Σ(x-m_x)^2) ÷ (√(Σ(y-m_y)^2)
※相関係数はxとyの共分散をそれぞれの標準偏差で割ったもの

03

参考:http://mcn-www.jwu.ac.jp/~yokamoto/ccc/stat/p22covcor/

この相関係数でデータ列1~4を計算すると下記の通り(エクセルではCORREL関数を使用)

テータ列 相関 相関係数
データ列1 正の相関 1
データ列2 負の相関 -1
データ列3 正の相関(数値が100倍) 1
データ列4 負の相関(数値が100倍) -1

相関係数は単位によらない値になっている。また、最大値が1、最小値が-1となるよう規格化されている

数関係数=Rとした時の表

範囲 いえること
1.0≧|R|≧0.7 高い相関がある
0.7≧|R|≧0.5 かなり高い相関がある
0.5≧|R|≧0.4 中程度の相関がある
0.4≧|R|≧0.3 ある程度の相関がある
0.3≧|R|≧0.2 弱い相関がある
0.2≧|R|≧0.0 ほとんど相関がない

相関がない場合

下記のようなデータ列5は年月日と全く関係のない値をとっているため、相関係数は0に近くなる

年月日 20140101 20140102 20140103 20140104 20140105 20140106 20140107 20140108 20140109 20140110
データ列5 相関なし 101 12 390 -236 1 12 342 -80 100 120

04

テータ列 相関 相関係数
データ列5 相関なし 0.018052993

相関が発見できない場合(シンプソンのパラドックス)

相関係数は変数同士の比例、反比例の関係のみ判断できるので、 下記のデータ列6のように半分に区間を区切れば相関があるデータについて見過ごしてしまう危険性がある

年月日 20140101 20140102 20140103 20140104 20140105 20140106 20140107 20140108 20140109 20140110
データ列6 相関が発見できない 101 102 103 104 105 105 104 103 102 101

05

テータ列 相関 相関係数
データ列6 相関があるのに発見できない 0
集団を2つに分けた場合にある仮説が成立しても、集団全体では正反対の仮説が成立することがある
 ⇒ シンプソンのパラドックス

単純にデータを切り出してその相関係数のみを考えることは危険!

【付録】相関係数はcosθを意味する 下記のようなベクトルを定義すると相関係数はcosθと同じことがわかる

  • x = (x1-m_x),(x2-m_x)・・・(xN-m_x)
  • y = (y1-m_y),(y2-m_y)・・・(yN-m_y) (以下略)

pythonアルゴリズムなどメモ

内容

自分がよく使うpython3の書き方のまとめ 大体はpython2でも使えるはず(?)

Tips

文字列を受け取って整数にする

N = int(input())

文字列を受け取って整数の配列にする

A = list(map(int, input().split()))

またはリスト内包表記を使って A = [int(a) for a in input().split()]

文字列を受け取って整数の変数に個別に代入

以下のようにリストの左辺をカンマ区切りで別々の変数にすればそれぞれに格納される

N, A, B = [int(x) for x in input().split()]
print(N)
print(A)
print(B)
3 53 94 ← 入力値
3
53
94

a ~ bまでfor

0~3をfor

for x in range(4):
  print(x)
0
1
2
3

1から始めて4まで出したい場合は 「1, 4+1」を指定する

for x in range(1, 5):
  print(x)
1
2
3
4

listに対してfor

l=[1, 2, 3]
for i in l:
    print(i)

結果

1
2
3

数字の各桁をすべて足す

考えた方法1.

以下ではlist(map(int, list(str(n))) で、 いったん数字nを文字列にしてからそれをlistで一文字ずつ区切ったリストにして、それらをintに戻している

n=12345
summary = 0
for i in list(map(int, list(str(n)))):
   summary += i

summary : 15

考えた方法2.こちらのほうがわかりやすい
s = 0
while n > 0:
    s += n % 10
    n //= 10

s: 15

ファイル読み込み

以下をopen.pyの名前で保存

import sys

args = sys.argv
#print(args[1])
path = args[1]

with open(path) as f:
    print(f.read())

python open.py <対象ファイル>

go言語の並行処理

概要

「Go言語による並行処理」を読んだのでメモ ※この本に書いてないことも以下では取り上げている

github.com

goroutineとsync

goroutineの書き方

一番単純なgoroutine(これは期待した通り動かない)

package main

import "fmt"

func main() {
        hello := func() {
                fmt.Println("hello")
        }
        go hello()
}

無名関数helloを定義して、「go 関数()」で実行する しかしこれは実行しても何も出力されない

$go run ex1.go
$

その理由は、main goroutineとhello goroutineはそれぞれ別個に動くので、 mainが終わる前に子の処理のhelloを待たないため。

そこで、意図したとおりにするためには、syncで待ち合わせをすることが一番簡単な実現方法となる

syncで待ち合わせをする(期待通りの挙動になる)

syncパッケージを使って待ち合わせをする場合は以下のようにする

  • wg.Add(待ちたい処理の数)を定義
  • hello goroutineが終わる前に、このgoroutineの終了を伝えるwg.Doneをdeferで実行
  • hello goroutineが終わるまで、wg.Wait()で待つ
$cat ex2.go
package main

import (
        "fmt"
        "sync"
)

func main() {
        var wg sync.WaitGroup

        wg.Add(1)
        hello := func() {
                defer wg.Done()
                fmt.Println("hello")
        }
        go hello()

        wg.Wait()
}

これを実行すると以下の通り出力される

$go run ex2.go                                                          
hello
$  

syncで待ち合わせをする(helloを定義しない)

上の例はより簡単に、以下のようにも書ける

  • かっこの最後に 「 }()」がついていることに注意
package main

import (
        "fmt"
        "sync"
)

func main() {
        var wg sync.WaitGroup

        wg.Add(1)
        go func() {
                defer wg.Done()
                fmt.Println("hello")
        }()

        wg.Wait()
}

syncで待ち合わせをする(helloを通常の関数として定義する)

helloを通常の関数として定義する場合には以下のように、WaitGroupをポインタとして渡すことが必要になる

package main

import (
        "fmt"
        "sync"
)

func main() {
        var wg sync.WaitGroup

        wg.Add(1)
        go hello(&wg)

        wg.Wait()
}

func hello(wg *sync.WaitGroup) {
        defer wg.Done()
        fmt.Println("hello")
}

syncで複数の待ち合わせをする

waitNum := 3 と定義して、その回数分だけ待つ

package main

import (
        "fmt"
        "sync"
)

func main() {
        var wg sync.WaitGroup

        waitNum := 3
        wg.Add(waitNum)
        hello := func() {
                defer wg.Done()
                fmt.Println("hello")
        }

        for i := 0; i < waitNum; i++ {
                go hello()
        }

        wg.Wait()
}

結果

hello
hello
hello

goroutineに引数を渡す

通常の関数と同様に、goroutineにも引数を渡せる

$cat ex3-2.go
package main

import (
        "fmt"
        "sync"
)

func main() {
        var wg sync.WaitGroup

        waitNum := 3
        wg.Add(waitNum)
        hello := func(i int) {
                defer wg.Done()
                fmt.Printf("hello %d\n", i)
        }

        for i := 0; i < waitNum; i++ {
                go hello(i)
        }

        wg.Wait()
}

結果

$go run ex3-2.go             
hello 2
hello 0
hello 1

channel

最も簡単なchannel

一番簡単な例

  • channelは複数のgoroutine間でデータを共有するためにあるので、ふつうこんな使い方はしない
package main

import "fmt"

func main() {
    ch := make(chan int, 1)

    // channelに5を送信
    ch <- 5

    // channelから整数値を受信
    i := <-ch
    fmt.Println(i)                     
}

実行結果

$go run chan.go
5

goroutineでのchannel

goroutineでchanelをやり取りする例

package main

import "fmt"

func main() {
    ch := make(chan int)                   
    go func() {
        // channelに5を送信
        ch <- 5
    }()

    // channelから5を受信
    fmt.Println(<-ch)
}

実行結果

5

複数のchannelを送受信することもできる

package main

import "fmt"

func main() {
    ch := make(chan int)
    n := 3
    go func() {
        for i := 0; i < n; i++ {
            ch <- i
        }
    }()

    for i := 0; i < n; i++ {
        fmt.Println(<-ch)
    }
}

実行結果

0
1
2

for rangeを使ったchannelの受信

for rangeを使ってchannelを受信する書き方もできる

ただし、for rangeを使って受信する場合、channelの終了がわからず永遠に待ち続けるので、goroutine側がこれ以上channelに送信しなくなった時点でchannelをcloseすることが必要

package main

import "fmt"

func main() {
    ch := make(chan int)
    n := 3
    go func() {
        defer close(ch) // channelの終了を送信
        for i := 0; i < n; i++ {
            ch <- i
        }
    }()

    for i := range ch {
        fmt.Println(i)
    }
}

実行結果

0
1
2

deferを忘れると、待ち続けてdeadlockになる

deferをつけない場合の実行結果

0
1
2
fatal error: all goroutines are asleep - deadlock!
channelのcloseを確認する

channelからは、2番目の返り値としてchannelがclose済みかそうでないかを表すbool値が取得できる 慣習的にこの2番目の返り値はokとする

上の例で言えば、chから得られた値がi、chがclose済みかどうかがok(closeされていればfalse)となる。このokはとっても取らなくてもいい i, ok := <-ch

参考 - A Tour of Go

上の例を、okがfalseとなるまでchの中身を出力し続けるようにした場合、以下のようになる

  • ※上のfor rangeを使った書き方より冗長だしミスると無限ループになるからめったに使う必要ないと思う
package main

import "fmt"

func main() {
    ch := make(chan int)
    n := 3
    go func() {
        defer close(ch) // channelの終了を送信
        for i := 0; i < n; i++ {
            ch <- i
        }
    }()

    for {
        i, ok := <-ch
        if !ok {
            fmt.Println("ch closed")
            return // このreturnを忘れると無限ループになる
        }
        fmt.Println(i)
    }
}

実行結果

0
1
2
ch closed

bufferなしchannel

bufferなしchannelとbufferつきchannelの挙動の違いについてシンプルにr例示している資料が意外とすぐに見つからなかった

まずbufferなしchannelの例

package main

import (
        "fmt"
        "time"
)

func main() {
        ch := make(chan int)
        go func() {
                defer close(ch)      
                defer fmt.Println("Producer Done")
                for i := 0; i < 5; i++ {
                        ch <- i
                        fmt.Printf("Sending: %d\n", i)
                }
        }()
    
        for i := range ch {
                fmt.Printf("Received %v\n", i)
                time.Sleep(1 * time.Second)
        }
       
}

実行結果

Sending: 0
Received 0
Received 1
Sending: 1
Received 2
Sending: 2
Received 3
Sending: 3
Received 4
Sending: 4
Producer Done
  • 1秒おきにSendingとReceivedが交互に出力されている

channelが満杯のときはchannelへの書き込みは(読み込みされるまで)ブロックされるので、bufferなしchannelは毎回読み込みをした後で追加の書き込みをしている

bufferつきchannel

bufferつきchannelは一度に送信できる件数を指定できる

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int, 4)
    go func() {
        defer close(ch)
        defer fmt.Println("Producer Done")
        for i := 0; i < 5; i++ {
            ch <- i
            fmt.Printf("Sending: %d\n", i)
        }
    }()

    for i := range ch {
        fmt.Printf("Received %v\n", i)
        time.Sleep(1 * time.Second)
    }

}

実行結果

Sending: 0
Sending: 1
Sending: 2
Sending: 3
Sending: 4
Producer Done
Received 0
Received 1
Received 2
Received 3
Received 4
  • buffer4に指定した場合、channelに対して先に4つ書き込みが終了し、あとからReceivedされていることがわかる
  • このように、bufferつきchannelは読み込みを待つことなく何件まで書き込めるかを指定できる

select

selectは複数のcaseに対して、それぞれの条件が等しく選択されるように、疑似乱数を使って配分している。

例えば、以下のようにc1, c2という2つのチャネルから合計1000回読み込むとする。

  • c1からのチャネルから読み込んだらc1Countを増やす
  • c2からのチャネルから読み込んだらc2Countを増やす
package main

import (
        "fmt"
)

func main() {
        c1 := make(chan interface{})
        close(c1)
        c2 := make(chan interface{})
        close(c2)
        var c1Count, c2Count int
        for i := 1000; i >= 0; i-- {
                select {
                case <-c1:
                        c1Count++
                case <-c2:
                        c2Count++
                }
        }

        fmt.Printf("c1Count: %d, c2Count: %d.\n", c1Count, c2Count)
}

このコードを何回も実行した結果を見てみると、c1, c2がほぼ同じ回数ずつカウントされていることがわかる

$go run select2.go
c1Count: 499, c2Count: 502.
$go run select2.go
c1Count: 498, c2Count: 503.
$go run select2.go
c1Count: 520, c2Count: 481.
$go run select2.go
c1Count: 513, c2Count: 488.
$go run select2.go
c1Count: 505, c2Count: 496.
$go run select2.go
c1Count: 502, c2Count: 499.
$go run select2.go
c1Count: 509, c2Count: 492.
$ 

for-selectでdefault処理

参考:Go by Example: Select

以下のように、チャネルを待っている間やりたい処理をdefaultに書くことでgoroutineの結果を待っている間他の処理ができる

for {
    select {
    case <-チャネル:
        <チャネルを受け取ったあとの処理>
    case <-チャネル2:
        <チャネル2を受け取ったあとの処理>
    default:
    }
    <チャネル1とチャネル2を受け取る前に行いたい処理>
}

for で無限ループをしながらselectでdoneが来るのを待ち続ける例

goroutineが5秒後にdoneにcloseを送るので、処理が終わる

それまではWaitを繰り返す

package main

import (
    "fmt"
    "time"
)

func main() {

    done := make(chan interface{})
    go func() {
        defer close(done)
        fmt.Println("Goroutine start")
        time.Sleep(5 * time.Second)
        fmt.Println("Goroutine end. send done")
    }()

    i := 0
    for {
        select {
        case <-done:
            fmt.Println("Receive done")
            return
        default:
        }

        i++
        fmt.Printf("Wait %d\n", i)
        time.Sleep(1 * time.Second)
    }
}

これを実行すると以下のようになる

$go run sample.go
Wait 1
Goroutine start
Wait 2
Wait 3
Wait 4
Wait 5
Goroutine end. send done
Receive done

ちなみに、以下のようにdefautlとselectの終端の}の間に処理を入れても同様の結果になるが、この処理が往々にして長くなることがあってselectの終端がわかりにくくなるので、自分はあまり好んで使う人はいないようだ

 default:
        <チャネル1とチャネル2を受け取る前に行いたい処理>
    }

timeout

Go by Exampleに書いてあるtimeoutの方法

Go by Example: Timeouts に書いてある方法

time.After(タイムアウトする時間) を設定してselect文で受け取ることで、時間がかかるgoroutineのタイムアウト処理ができる

以下の例では、 - 2秒かかるgoroutineの結果c1が1秒のtimeout制限に引っかかってtimeout 1が出力され、 - 同じく2秒かかるgoroutineの結果c2が3秒のtimeout制限以内に終わるので、result 2が出力される

package main

import (
    "fmt"
    "time"
)

func main() {
    c1 := make(chan string, 1)
    go func() {
        time.Sleep(2 * time.Second)
        c1 <- "result 1"
    }()
    select {
    case res := <-c1:
        fmt.Println(res)
    case <-time.After(1 * time.Second):
        fmt.Println("timeout 1")
    }

    c2 := make(chan string, 1)
    go func() {
        time.Sleep(2 * time.Second)
        c2 <- "result 2"
    }()
    select {
    case res := <-c2:
        fmt.Println(res)
    case <-time.After(3 * time.Second):
        fmt.Println("timeout 2")
    }
}

実行結果

timeout 1
result 2

上のtimeout方法の問題点

上の方法には一つ問題があって、timeout 1と出てもgoroutineに切り出した処理は自動で止まらないということがある

ためしに以下のようにc1で受け取っていた部分を以下のようにtaskというgoroutineにして、task関数の中では0から999999まで数字を出力し続けるようにしてみる - また、最初と最後の時間差をとって、処理時間を計測してみる (結果は長くなるので注意)

package main

import (
    "fmt"
    "log"
    "time"
)

func main() {
    start := time.Now()
    select {
    case res := <-task():
        fmt.Println(res)
    case <-time.After(1 * time.Second):
        fmt.Println("timeout 1")
    }
    log.Printf("Total time: %fs", time.Since(start).Seconds())
}

func task() <-chan string {
    res := make(chan string)
    go func() {
        defer close(res)
        for i := 0; i < 1000000; i++ {
            fmt.Println(i)
            time.Sleep(1 * time.Nanosecond)
        }
        res <- "done!"
    }()
    return res
}

The Go Playground

実行結果

0
1
2
(省略)
475135
475136
475137
timeout 1
2019/09/29 06:11:19 Total time: 12.296678s

おかしなことが起きた! 実行結果はマシンの性能にもよると思うが、「timeout 1」まで出した後でtimeoutが出力されて、「 Total time: 12.296678s」となっている。

しかも恐ろしいことに2回目に実行すると、今度は「timeout 1」すら出ずに12秒かかっている。

489587
489588
489589
2019/09/29 06:13:37 Total time: 12.209647s

3回目もまた結果が変わる

489490
489491
489492
2019/09/29 06:14:39 Total time: 12.120974s

timeoutとなっても、すでに起動してfmt.Println(i) を出力し続けるgoroutineは勝手に暴走して、それが終わるタイミングは全然予測できていない

これをtimeout時間の通り1秒で終了させたい場合は以下のようにcontextやdoneチャネルを使うといい。

timeoutでcontext cancelを行う場合

上のコードをcontextを用いて書き直したものが以下となる。

timeout時刻になるとcontextのcancel処理が飛んで、1秒で処理が終わっていることがわかる

package main

import (
    "context"
    "fmt"
    "log"
    "time"
)

func main() {
    start := time.Now()
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    res := task(ctx)
    select {
    case <-res:
        fmt.Println(res)
    case <-time.After(1 * time.Second):
        log.Println("timeout after 1s")
        cancel()
        log.Println(<-res)
    }
    log.Printf("Total time: %fs", time.Since(start).Seconds())
}

func task(ctx context.Context) <-chan string {
    res := make(chan string)
    go func() {
        defer close(res)
        for i := 0; i < 1000000; i++ {
            select {
            case <-ctx.Done():
                log.Printf("context cancelled. count: %d", i)
                res <- fmt.Sprintf("error: %s", ctx.Err())
                return
            default:
            }
            fmt.Println(i)
            time.Sleep(1 * time.Nanosecond)
        }
        res <- "done!"
    }()
    return res
}

The Go Playground

実行結果

(省略)
38503
38504
38505
38506
38507
2019/09/29 06:30:35 timeout after 1s
2019/09/29 06:30:35 context cancelled. count: 37841
2019/09/29 06:30:35 error: context canceled
2019/09/29 06:30:35 Total time: 1.000472s

contextの使い方は公式は以下。ほかにもネット上にたくさんあるので、以下で簡単な説明だけ記載しておく。 Go Concurrency Patterns: Context - The Go Blog

簡単に説明
  • コードの最初でcancel 機能付きのcontextを宣言する
  • 「defer cancel()」によって、プログラム終了時に確実にすべてのcontextがキャンセルされるようにする
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
  • task関数にこのcontextを渡して(第一引数として渡すのがセオリー)、for 文の1ループごとに、「ctx.Done()」が来ていないかチェックしている
  • このプログラムでは 「ctx.Done()」を受け取ったらlogに「context cancelled. count」とその時のループ番号を出力している
  • さらにresにエラー原因として「 ctx.Err()」を返してreturnでgoroutineを終える
  • このreturnがないとDoneを受け取ってもgoroutineが終わらないので必要
for i := 0; i < 1000000; i++ {
    select {
    case <-ctx.Done():
        log.Printf("context cancelled. count: %d", i)
        res <- fmt.Sprintf("error: %s", ctx.Err())
        return
    default:
    }
    fmt.Println(i)
    time.Sleep(1 * time.Nanosecond)
}
  • 呼び出し側のmain内では、「res := task(ctx)」としてtaskの戻り値(channel)をいったんres変数に定義
  • time.Afterの制限時間内に処理が終われば、「case <-res」に入る
  • time.Afterの制限時間を過ぎたら、「timeout after 1s」をlogに表示させて、contextのcancel関数を呼び出す(重要)
  • 「log.Println(<-res)」には、task関数内で詰めたctx.Err()の内容が出力される「error: context canceled」
    • 「<-res」とすることで、cancelをした後のtask関数が返すエラーをきちんと受け取ってから終わるように待ち合わせをしている
res := task(ctx)
select {
case <-res:
    fmt.Println(res)
case <-time.After(1 * time.Second):
    log.Println("timeout after 1s")
    cancel()
    log.Println(<-res)
}

contextを使うことで、goroutineを安全に終了させることができる

pipeline、fan-in、fan-out

go言語のpipeline、fan-in、fan-out - ludwig125のブログ

複数のgoroutineのエラーハンドリングをする

ludwig125.hatenablog.com

同時並列数を制御する

ludwig125.hatenablog.com

シグナル処理

ludwig125.hatenablog.com

GoogleAppEngineでgoroutineを使う

GoogleAppEngineでgoroutineを使った例 ludwig125.hatenablog.com