Go言語の基本構文と並行処理機能について解説
Go言語は、Googleが開発したシンプルで高速なプログラミング言語です。
軽量な構文と強力な並行処理機能を持ち、初学者から経験者まで幅広く利用されています。
この記事では、Go言語の基本的な特徴と使いやすさに焦点を当てて紹介します。
Go言語の基本構文
変数と定数の定義
型宣言と型推論
Go言語では、変数宣言において明示的な型指定と型推論が利用できます。
明示的な型宣言の場合は読みやすさが向上し、型推論では記述を簡略化できます。
以下のサンプルコードでは、文字列型の変数と型推論を利用した整数変数を定義しています。
package main
import "fmt"
func main() {
// 明示的な型宣言
var message string = "こんにちは、Go!"
// 型推論を利用
number := 42
fmt.Println(message, number)
}
こんにちは、Go! 42
定数の利用法
定数は値が変化しないことを保証できるため、プログラムの安全性や可読性の向上に役立ちます。
以下では、定数を利用して円周率を表現する例を示しています。
package main
import "fmt"
func main() {
const pi = 3.14
fmt.Println("定数pi:", pi)
}
定数pi: 3.14
制御構文と文法
if文、switch文の使い分け
if文は条件式に基づく処理の分岐に、switch文は複数のケース分岐時に有用です。
サンプルコードでは、数値が偶数か奇数かをif文で判定し、switch文で数値の特徴に応じた処理を行っています。
package main
import "fmt"
func main() {
num := 7
// if文による条件分岐: 偶数と奇数の判定
if num%2 == 0 {
fmt.Println("偶数です")
} else {
fmt.Println("奇数です")
}
// switch文による分岐処理
switch num {
case 1, 3, 5, 7, 9:
fmt.Println("シングル数字です")
default:
fmt.Println("その他の数字です")
}
}
奇数です
シングル数字です
forループの基本構造
forループは、Go言語における唯一のループ構文であり、反復処理に利用できます。
サンプルコードでは、1から5までの整数の和を計算しています。
package main
import "fmt"
func main() {
sum := 0
for i := 1; i <= 5; i++ {
sum += i
}
fmt.Println("合計:", sum)
}
合計: 15
関数の基本
複数値の返却
Go言語は、関数から複数の値を返却する機能を備えています。
以下の例では、2つの整数を引数とし、その商と余りを返す関数divide
を実装しています。
package main
import "fmt"
// divideは整数aとbを受け取り、商と余りを返す関数です。
func divide(a, b int) (int, int) {
quotient := a / b
remainder := a % b
return quotient, remainder
}
func main() {
q, r := divide(10, 3)
fmt.Println("商:", q, "余り:", r)
}
商: 3 余り: 1
無名関数とクロージャ
無名関数は、関数リテラルとも呼ばれ、変数に代入することでクロージャとして利用できます。
以下の例では、呼び出すたびに内部のカウンターが増加するクロージャを定義しています。
package main
import "fmt"
func main() {
// クロージャとしてカウントアップする無名関数を定義
counter := func() func() int {
count := 0
return func() int {
count++
return count
}
}()
fmt.Println("カウント:", counter())
fmt.Println("カウント:", counter())
}
カウント: 1
カウント: 2
エラーハンドリング
エラー型の確認と対処
Go言語では、関数の返却値としてerror
型を返すことでエラーハンドリングを行います。
サンプルコードでは、引数が負の数の場合にエラーを返す簡易的な平方根計算関数を実装し、エラーが発生した際の処理例を示しています。
package main
import (
"errors"
"fmt"
)
// sqrtは引数xが負の値の場合、エラーを返す関数です。
// 実際の平方根計算にはmath.Sqrtを利用するのが一般的ですが、ここでは簡易な例を示します。
func sqrt(x float64) (float64, error) {
if x < 0 {
return 0, errors.New("負の値は無効です")
}
// 簡単な近似値としてx/2を返す
result := x / 2
return result, nil
}
func main() {
value, err := sqrt(-4)
if err != nil {
fmt.Println("エラー:", err)
} else {
fmt.Println("平方根:", value)
}
}
エラー: 負の値は無効です
並行処理の機能
goroutineの基礎
ゴルーチンの起動方法
Go言語では、軽量スレッドであるゴルーチンにより容易に並行処理を実現できます。
サンプルコードでは、go
キーワードを用いて関数をゴルーチンとして起動しています。
package main
import (
"fmt"
"time"
)
func printMessage() {
fmt.Println("ゴルーチンで実行中")
}
func main() {
go printMessage()
// 少し待機してゴルーチンの実行を確認
time.Sleep(100 * time.Millisecond)
fmt.Println("メイン関数終了")
}
ゴルーチンで実行中
メイン関数終了
利用時の注意点
並行処理を行う際、データ競合などの問題に注意が必要です。
以下のサンプルコードは、複数のゴルーチンで共有変数を更新する際にsync.Mutex
を利用して排他制御を行っています。
package main
import (
"fmt"
"sync"
)
var counter int
// addはグローバルなcounterを安全に増加させる関数です。
func add(wg *sync.WaitGroup, m *sync.Mutex) {
defer wg.Done()
m.Lock()
counter++
m.Unlock()
}
func main() {
var wg sync.WaitGroup
var m sync.Mutex
for i := 0; i < 5; i++ {
wg.Add(1)
go add(&wg, &m)
}
wg.Wait()
fmt.Println("最終カウンター:", counter)
}
最終カウンター: 5
channelによるデータ通信
channelの生成と操作
チャネルは、ゴルーチン間でデータを安全に受け渡すための仕組みです。
以下の例では、チャネルを作成し、ゴルーチンからメッセージを送信してメイン関数で受信しています。
package main
import "fmt"
func main() {
messageChan := make(chan string)
go func() {
// チャネルにメッセージを送信
messageChan <- "チャネルからのメッセージ"
}()
msg := <-messageChan
fmt.Println(msg)
}
チャネルからのメッセージ
BufferedとUnbufferedの違い
チャネルはバッファ付きとバッファなしの2種類があり、バッファ付きの場合、指定したバッファサイズまでの送信が非同期で行われます。
以下のサンプルでは、バッファ付きチャネルを利用して送信がブロックされない様子を示しています。
package main
import "fmt"
func main() {
// バッファサイズ2のチャネルを生成
bufferedChan := make(chan int, 2)
// 送信後すぐに受信しなくてもブロックされない
bufferedChan <- 100
bufferedChan <- 200
fmt.Println("受信:", <-bufferedChan)
fmt.Println("受信:", <-bufferedChan)
}
受信: 100
受信: 200
select文による処理制御
select
文を用いることで、複数のチャネルの受信操作を待ち受け、どのチャネルからデータが到着したかに応じた処理を実装できます。
下記の例では、複数のゴルーチンからのメッセージをselect
文で受け取っています。
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(50 * time.Millisecond)
ch1 <- "チャンネル1からのメッセージ"
}()
go func() {
time.Sleep(100 * time.Millisecond)
ch2 <- "チャンネル2からのメッセージ"
}()
// どちらか早く到着したチャネルのメッセージを受信
select {
case msg := <-ch1:
fmt.Println(msg)
case msg := <-ch2:
fmt.Println(msg)
}
}
チャンネル1からのメッセージ
syncパッケージの活用
WaitGroupによる同期管理
snyc.WaitGroup
は、複数のゴルーチンの終了待ち合わせに利用され、処理の完了タイミングを正確に把握できます。
下記の例では、3つのワーカーゴルーチンを起動し、全ての処理完了を待機しています。
package main
import (
"fmt"
"sync"
)
// workerはワーカーIDを表示する関数です。
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("Worker", id, "開始")
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("全てのWorker終了")
}
Worker 1 開始
Worker 2 開始
Worker 3 開始
全てのWorker終了
Mutexなどの排他制御手法
snyc.Mutex
を利用すると、複数のゴルーチンによる共有データへの同時アクセスを排除できます。
以下の例では、Mutex
を用いてグローバル変数count
の安全な更新を行っています。
package main
import (
"fmt"
"sync"
)
var (
count int
mtx sync.Mutex
)
// incrementはカウンターを1増加させる関数です。
func increment(wg *sync.WaitGroup) {
defer wg.Done()
mtx.Lock()
count++
mtx.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("カウンター値:", count)
}
カウンター値: 5
開発環境とツールの活用
ビルド・実行コマンドの基本
go run, go build, go testの利用方法
Go言語では、シンプルなコマンドでプログラムのビルドや実行、テストを行うことが可能です。
下記のサンプルコードは、実行時の基本的な出力例を示しています。
実際の環境では、以下のようなコマンドを利用してください。
package main
import "fmt"
func main() {
fmt.Println("go run main.go でプログラムを実行")
}
go run main.go でプログラムを実行
モジュール管理
Go Modulesの基本操作
Go Modulesは、依存関係の管理を行うための仕組みです。
プロジェクトディレクトリでgo mod init
を実行すると、モジュール管理が開始されます。
サンプルコードでは、シンプルなメッセージを出力してモジュール管理の利用を示しています。
package main
import "fmt"
func main() {
fmt.Println("Go Modulesを利用して依存関係を管理")
}
Go Modulesを利用して依存関係を管理
コード整形とリント
fmtパッケージとgo fmtの利用ポイント
Go言語では、fmt
パッケージを利用した文字列整形や、標準ツールのgo fmt
でコードのフォーマットを自動整形できます。
下記の例では、fmt.Sprintf
を利用して整形した文字列を出力しています。
package main
import "fmt"
func main() {
name := "Go"
version := "1.18"
// 整形した文字列を作成
message := fmt.Sprintf("言語%sのバージョン%s", name, version)
fmt.Println(message)
}
言語Goのバージョン1.18
まとめ
この記事では、Go言語の基本構文や並行処理、ツールの活用方法について解説しました。
各項目で型宣言・型推論、制御構文、複数値返却、無名関数、エラーハンドリング、goroutineやchannel、select、WaitGroup、Mutexなどの基本的な使い方を理解できる内容でした。
今すぐ自分のコードに取り入れて、実践的なスキル向上にチャレンジしてみてください。