20200929のGoに関する記事は4件です。

【Golang】Go言語 channel(チャネル)について

channelとは?

Goroutineは並行実行されているので、通常だとデータのやりとりができません。
しかし、channelというのををつかうとデータのやりとりができます。

トンネルをつなぐようなイメージです!

package main

import (
    "fmt"
)

func goroutine1(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    //チャネルにデータを送信
    c <- sum
}

func main() {
    s := []int{1, 2, 3, 4}
    c := make(chan int)
    go goroutine1(s, c)
      //送られてきたデータを受信
    p := <-c
    fmt.Println(p) // => 10
}

c <- sum でcに値を送信し、p := <-c で受信しています。ここでは受信するまでブロッキングされていて、pはsumを受信するまで待っている状態です。sync.Waitと同じような状態ですね!

Buffered channels

バッファとはプログラムで処理を行なうときに、データを一時的に格納するためのメモリー領域のことをいいます。
チャネルに一時データを保存するイメージです。

package main

import "fmt"

func main() {
    //2つまでバッファを保存できる
    ch := make(chan int, 2)
    ch <- 100
    //len()でチャネルの長さを調べられる
    fmt.Println(len(ch))
    ch <- 200
    fmt.Println(len(ch))
    //3つめのデータを送信しようとするとエラーになる
    ch <- 300
    fmt.Println(len(ch)) // => fatal error: all goroutines are asleep - deadlock!
}
実行結果
1
2
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
    /tmp/sandbox795806942/prog.go:11 +0x16f

2つまでしか保存できないので、3つめを送信しようとすると当然ですがエラーになります。(デッドロック)

↓にように手前で一個チャネルを取りだしてから300を送信すれば、エラーにならなくなります。

func main() {
    ch := make(chan int, 2)
    ch <- 100
    fmt.Println(len(ch))
    ch <- 200
    fmt.Println(len(ch))

    x := <-ch
    fmt.Println(x)
    ch <- 300
    fmt.Println(len(ch))
}
実行結果
1
2
100
2

次に以下のコードをご覧ください↓

package main

import "fmt"

func main() {
    ch := make(chan int, 2)
    ch <- 100
    fmt.Println(len(ch))
    ch <- 200
    fmt.Println(len(ch))

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

これはチャネルをrangeでループさせようとしているのですが、これはエラーになります。

実行結果
1
2
100
200
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
    /tmp/sandbox892357489/prog.go:12 +0x1f3

これは存在しない3つ目のチャネルまでループしようとしてしまうからです。
2つ目のチャネルでループ処理を止めるにはチャネルを一旦クローズする必要があります。
そのためにはclose(ch)を記述する必要があります。

package main

import "fmt"

func main() {
    ch := make(chan int, 2)
    ch <- 100
    fmt.Println(len(ch))
    ch <- 200
    fmt.Println(len(ch))
    close(ch)
    for c := range ch {
        fmt.Println(c)
    }
}
実行結果
1
2
100
200

最後まで読んでいただきありがとうございます!

ご指摘などございましたら、コメントいただけると幸いです!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ISUCON10予選に参加してきました!

ISUCON 初参加!

ISUCON10に参加してきました!
こういったコンテスト(競技プログラミング)自体初めての参加だったのでとても有意義な体験でした!
この記事では自身の体験とともに、 ISUCONって一体何なのかについてもご紹介できればなと思います。

ISUCON とは :thinking:

いい感じにスピードアップするコンテスト (Iikanjini Speed Up CONtest)
の略で、お題のWebサービスを、決められたレギュレーションの中で限界まで高速化を図るチューニングバトルです。

参加者はただひたすらにお題として与えられたWebシステムを高速化するのみ、 限られた時間 (8時間!!)
を使って、いかにそのシステム全体を読み解き、理解し、パフォーマンスを上昇させるかに尽力します。

予選と本戦に別れており、前哨戦である予選を勝ち抜けるのは 全体のうち 30チームのみ!
最近では学生さんの参加があることもあり, 一般枠と学生枠に更に別れており
一般は25チーム学生さんは5チームが選出されます。

優勝賞金は100万円! という太っ腹な大会です。

これまで 計10回開催されており、今回参加したのが記念すべき第10回でした :clap:

構成

  • 基本的に 複数の言語でシステムが実装されているのでそのうちのどれかを選んでチューニングしていきます。
オンライン予選 利用言語比率
利用率の全体ランキングは以下の通りです。

Go      276組 59.0%
Ruby     81組  17.3%
Python   47組  10.0%
Nodejs   29組  6.2%
PHP     18組  3.8%
Rust      8組  1.7%
Perl       7組  1.5%
Elixir     1組  0.2%
original-ruby 1組  0.2%
  • 1~3人までのチームを組み、問題に取り組んでいきます。

    • アプリ、インフラ、分析&司令 といったような構成がオーソドックス
  • スコアの測定には独自のベンチマークを使用

    • どんな手段でもよいので、以下に高得点のスコアをベンチマークで算出するかがカギ ※ ベンチマークはいじっちゃだめ

今回の問題

issumo という イスと物件を検索し、購入&資料請求できるWebサイトが今回の問題として与えられました。
機能としては以下が主でした。

  • イスの条件検索 && 物件の条件検索
    • (様々なパラメータを設定でき、それに一致した物件を検索できる)
  • なぞって検索 (地図上にマウスでドラッグした指定範囲で物件を検索できる)
  • イス、物件のそれぞれの最低価格の一覧表示
  • 物件のレコメンド

etc...

今回はログインなどの状態での処理などがなかった為、
純粋にシステムのボトルネックが何なのかを突き止めていくような問題だったと思います。
個人的には 特になぞって検索がすごかった! なぞった範囲の緯度経度をから範囲内の物件情報を表示するUIがよかった...!

当日やったこと

  • アプリ初期動作確認

    • 僕の担当はアプリ部分だったので競技が始まってから、まずアプリの動作確認
    • 今回は踏み台サーバー経由でのアクセスしかできず うまく接続できなかったため足踏みすることに...。
  • その後

    • インフラのチームメイトがソースコードバックアップとgit管理してくれたので、sshはお任せ
    • ソースコード読み込み,関数ごとのコメント付け, エンドポイントの書き出しをしていました。
  • DB(mysql周り)

    • DBがボトルネックっぽいことがわかったので、index貼ってexplainで計測を繰り返したりいました。
    • ただ、indexを貼っても特に結果が変わらず、どうしたものかと頭を抱えたりしてもいました :expressionless:
    • チームで相談しもともとはmysql5.7で動いていたものをmysql8に換装 >> 初期スコアから半減するという結果に :scream:
    • ベンチマーカー回すとデータが初期化され、indexも消えると悩んでいたので schema.sql のテーブル定義に追加するよう提案
  • SQL&レスポンス改善

    • 雑に COUNT(*) になっているSQLや SELECT *になってるSQLを改善
    • ランディングページで毎回読み込んでいるクエリをRedisに投入してそこから取得するように変更していたりしました。
    • OR条件を5連結していたクエリを union で書き直していましたが、結果的にベンチが通らず断念...

まとめ

こうしてまとめてみると当日全然動けてなかったな、というのが顕著にわかります。
自身が普段ほぼフロントエンド専任なのもありますが、圧倒的にバックエンド関連の知識と経験が欠如しているなと実感しました。正直めっちゃ悔しい。

反省点&感想

あとから振り返るとLike検索があった部分N+1っぽい部分(なぞって検索)をコメントつけている途中で見つけていたのに
他の作業で埋没してしまったなぁというのが反省点としてあります。
また、クエリの改善策がこれといって思いつかず、呆然としてしまった時間があったのでそこがなければ
もう少し手を動かせたのではないか思います。
普段Go触ってないからGoでバックエンドサーバ作ってみるのもいいかもしれません。
何れにせよ実力不足を痛感したISUCONでした...。でもめっちゃ楽しかった!普段触れない言語だったり、いろんな知識を取得できるいい機会でした。
Denoでの実装もあったので typescriptをバックエンドで使用するのもアリだなと思った次第です。

今回DBが物件とイスしかない上、お互いをJOINしているSQLもなかったため、それぞれDBを割り当てるという荒業を行っているチームもありました!思いつくアイデアが既にすごい。。。!

普段土日で開催するものを土曜に集約し、500組もの参加者を捌いた運営さんほんとすごいなと思いました。本当にありがとうございました :smile:

予選問題の解説

http://isucon.net/archives/55025156.html にて公開されています!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go】基本文法③ ポインタ

udemy講座の「ポインタ」編を見終わったので、まとめとふりかえり。

今回もコンソール上に出力しながら基本的な動作を学習しました。

前提

・udemyで現役シリコンバレーエンジニアが教えるGo入門 + 応用でビットコインのシストレFintechアプリの開発)を視聴してGoを学習中
 ※「ブログ等で数行のコードの紹介は可能」とのことなので、講義で扱ったコードを引用しています。

・開発環境はVSCode + プラグイン(Goのバージョンは1.15)
・OSはWindows
・普段はJavaを使用しています。

ポインタ

・ポインタ型の変数を指定すると、メモリの中身を直接上書きできる。
・理解しにくい場合は、「&」「*」をつけて適当に動かしつつ理解すると良い。
・ポインタ型の生成時は、"make"ではなく"new"を使用する。

func one(x int) {
    // main関数からは値だけを受け取る
    fmt.Println(&x)
    x = 1
}

func two(x *int) {
    // ポインタ型の箱でxのアドレスを受け取る。
    fmt.Println(&x)
    // アドレスの中身を直接上書き
    *x = 2
}
func main() {
    var n int = 100

    // メモリのアドレスが出力される
    fmt.Println(&n)

    // integerのポイント型 アドレスが入る(C言語と同じ)
    var p *int = &n
    fmt.Println(p)

    // アドレスが指しているメモリの中身を見たいとき
    fmt.Println(*p)

    one(n)
    // oneの引数に入れたnはコピーなので、関数内のnには影響なし
    fmt.Println(n) // 100が出力される

    two(&n)
    fmt.Println(n)  // 2が出力される

}

この「ポインタ」がいまいち理解できなかったのですが、「Goで学ぶポインタとアドレス」の記事と記事内で引用されているリンク先での説明が分かりやすかったです。C言語由来の概念なので、数々の先輩が苦労して積み上げたナレッジがあるようです。

struct(構造体)

・"<型名> struct"で構造体を定義できる。
・値を保持するにはポインタ型で定義する必要がある。

type Vertex struct {
    // 変数は大文字スタート
    // 小文字にするとprivate扱い
    X int
    Y int
    S string
}

func changeVertex(v Vertex) {
    v.X = 1000
}

func changeVertex2(v *Vertex) {
    v.X = 1000
    (*v).X = 1000 // 本来こちらが正しい書き方だが、structの場合は自動でポインタの実体の方を参照してくれる。
}

func prg3() {
    // 構造体の生成
    v := Vertex{X: 1, Y: 2}  // 定義していない変数には初期値が入る
    changeVertex(v)          // 構造体を値渡しで渡す(コピー)
    fmt.Println(v)           // 出力結果:{1 2 }
    v.X = 100                // 変数の指定も可能
    fmt.Println(v.X, v.Y)    // 出力結果:100 2 

    v2 := &Vertex{1, 2, "text"}
    changeVertex2(v2)
    fmt.Println(v2)          // 出力結果:&{1000 2 text}

    var v3 Vertex
    fmt.Println(v3)          // nilではないので注意。初期値が入ったものがコンストラクトされる
                             // 出力結果:{0 0 }
    v4 := new(Vertex)
    fmt.Println(v4)          // ポインタ型 出力結果:&{0 0 }

    v5 := &Vertex{}
    fmt.Println(v5)          // ポインタ型 v6と同じ内容だがこちらの方がポインタ型と判別しやすい
}

所感

・ポインタの概念が理解しにくい。
・structはjavaのオブジェクトに近い感覚。


<前回の投稿>

基本文法① 定義

基本文法② ステートメント

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go/GinでHTTPSサーバにする方法

Go/GinでHTTPSサーバにする方法をGinのGithubから見つけたので、
メモ代わりに記載しておきます。

[環境]
go version go1.15.2 darwin/amd64

[参考URL]
https://github.com/gin-gonic/gin
README.md

HTTPサーバの場合

httpd.go
r := gin.Default()
r.GET("/", func(c *gin.Context) {
    c.String(200, "Hello Gin!!")
})
r.Run()

HTTPSサーバの場合

httpd.go
r := gin.Default()
r.GET("/", func(c *gin.Context) {
    c.String(200, "Hello Gin!!")
})
r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key")

上記のように、
「r.Run」を「r.RunTLS」に変更すればhttpsサーバになりました。
r.RunTLSの第一引数はポート番号、第二第三引数は証明書のパスを指定してます。

以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む