半年ほど前から何となくgolang
を書き始めてみた。
業務ではjava
ばかり書いていたので、気分転換にと思って書き始めたが、どうせならしっかり言語特性を理解したいなと思い、golang
について調べてみた。
【参考にしたサイト】
・ golang公式(FAQ)
・Go言語らしさとは何か?を題材にした記事
・golangの並列処理について理解しやすい記事
golangが作られた背景
- 動的型付け言語のプログラミング容易性、静的型付け言語の安全性・効率的なコンパイルを一つの言語で享受したい。
- マルチコアプロセッサを生かした優れた非同期処理による実行効率を目指した。
- 本当に開発者が必要だと思う機能のみを提供するように、言語仕様をリッチにしないことにより、開発者の迷いを無くし、生産性の高い開発をサポートしたい。
golangの強力な機能
interface
-
golang
のinterface
はinterface
に定義したメソッドを実装しただけでinterface
を実装していると見なされる。 - これは
interface
を「ある概念を表すための振る舞いを定義するもの」としてではなく「概念を扱う側がどう扱いたいかを定義するもの」として扱えるということだと理解。 - 「依存性逆転の法則」を正確に、コンパクトに実現できるようになっている。
package main
import (
"fmt"
"strconv"
)
// メイン処理
func main() {
PrintGame(Soccer{11, "サッカー", 120})
PrintGame(Chess{9, "チェス", 9})
}
func PrintGame(g Game) {
fmt.Println(g.getGameName() + "は" + strconv.Itoa(g.getPlayerMemberCount()) + "人で行うゲームです")
}
// ゲームインターフェース(メイン処理で使いたいもののみを定義)
type Game interface {
getPlayerMemberCount() int
getGameName() string
}
// サッカー構造体
type Soccer struct {
playerMemberCount int
gameName string
playTime int
}
func (s Soccer) getPlayerMemberCount() int {
return s.playerMemberCount
}
func (s Soccer) getGameName() string {
return s.gameName
}
// チェス構造体
type Chess struct {
playerMemberCount int
gameName string
boardSize int
}
func (c Chess) getPlayerMemberCount() int {
return c.playerMemberCount
}
func (c Chess) getGameName() string {
return c.gameName
}
func (c Chess) getBoardSize() int {
return c.boardSize
}
コンポジション
- 継承という概念を無くし、オブジェクト志向言語として扱いたい場合に、継承ではなく移譲を強制し、コードの複雑さを取り払う。
- 個人的には多重継承された実装は可読性が低く、スパゲティコードの温床になり得ると思っているので、この仕様は好み。
package main
import (
"fmt"
"time"
)
// メイン処理
func main() {
order := Order{"パン", OrderDetail{100, "buy", time.Now()}}
fmt.Println(order.getItemName())
// 注文に埋め込んだ構造体のメソッドを直接呼び出せる
fmt.Println(order.getOrderPrice())
fmt.Println(order.getBuySellType())
fmt.Println(order.getOrderDateTime())
}
// 注文構造体
type Order struct {
itemName string
// 注文明細を注文に埋め込み
OrderDetail
}
func (o Order) getItemName() string {
return o.itemName
}
// 注文明細構造体
type OrderDetail struct {
orderPrice int
buySellType string
orderDateTime time.Time
}
func (d OrderDetail) getOrderPrice() int {
return d.orderPrice
}
func (d OrderDetail) getBuySellType() string {
return d.buySellType
}
func (d OrderDetail) getOrderDateTime() time.Time {
return d.orderDateTime
}
非同期処理
golang
には並行処理を行える機能としてgoroutine
が実装されています。
goroutine
は「並列処理」ではなく「並行処理」を行う機能です。(自分も曖昧だった概念のためこの機にちゃんと理解した。。)
goroutine
が他の言語の並行処理に比べて優れていると言われている点は二つ。
- 並行処理の確保するメモリが軽量
- 並行処理の実行速度
この二点について調べてみました。
並行処理の確保するメモリが軽量
一つのgoroutine
が起動時に確保するメモリ容量と、メモリ確保の方法に起因しています。
一般に、スレッドを複数起動して処理を行う場合、一つのスレッドを起動するにあたり、プロセス内のメモリ領域がスレッド同士で干渉し合わないように大きめにメモリ領域(1MBくらいらしい)を確保します。
それに比べてgoroutine
はスタック領域に2KBほどを確保するのみだそうです。そこからgoroutine
として動作する関数の必要とするメモリのみをヒープ領域に割り当てていく仕組みになっている。
そのため、何千何万とスレッドを立てるには膨大なメモリが必要になりますが、goroutine
であればそんな心配はしなくて良いということ。だと思う。
並行処理の実行速度
これはスレッドがコンテキストスイッチを行う時に比べて、goroutine
の行うコンテキストスイッチの方がコストが少ないということに起因している。
スレッドであればCPUの複数のレジスタから複数のレジスタにスレッドの扱っているデータを退避させるという作業が発生しますが、goroutine
であればせいぜい3,4種類のレジスタの退避を行えば良いだけなのです。
また、goroutine
のスケジューラはgoroutine
のブロッキング処理を検知すると別の goroutine
の処理を実行し始めるため、システムリソースを無駄にしないよう設計されています。
最後に
ここまでgolang
の機能や設計思想について調べた結果を記載しましたが、調べる中で、かなりスリム化された言語であると感じました。
業務ではjava
を使用していますが、色々便利な機能があったり、汎用的に実装するための機能が提供されています。
が、私を含め多くのメンバーがjava
のそういった機能を使いこなせているかというとそうではないと感じています。
それどころか、そういった機能を使うことにより保守しにくいコードを量産してしまっているのでは。。
そのため、golang
のように面白みがない言語だと揶揄されることがあっても、保守しやすいコードを効率的に書くことをある程度強制するような言語に触れることは、学びになると思いました。
これからもgolang
を趣味ではありますが、書いていこうと思います。