SSL証明書・Go言語開発・Linuxサーバー構築・セキュリティ
Go 言語の最大の特徴の一つが、軽量スレッドである goroutine と、
goroutine 間の通信手段である channel による並行処理モデルです。
「メモリを共有することで通信するのではなく、通信することでメモリを共有せよ」という
Go の設計思想に基づいています。
本ドキュメントでは基本から実用パターンまでを実例で解説します。
関数呼び出しの前に go キーワードを付けるだけで goroutine として非同期実行されます。
OS スレッドより桁違いに軽量(初期スタック 2KB 程度)で、数千〜数十万の goroutine を同時に扱えます。
package main
import (
"fmt"
"time"
)
func printNumbers(id int) {
for i := 0; i < 3; i++ {
fmt.Printf("goroutine %d: %d\n", id, i)
time.Sleep(10 * time.Millisecond)
}
}
func main() {
go printNumbers(1)
go printNumbers(2)
// main が終了すると goroutine も終了するため待機
time.Sleep(100 * time.Millisecond)
fmt.Println("done")
}
time.Sleep で待つのは本番コードには不適切です。
後述の sync.WaitGroup や channel で適切に終了を待ちます。
channel は goroutine 間で値を安全に受け渡すキューです。
make(chan T) で unbuffered(同期)、make(chan T, n) で buffered(バッファあり)を作ります。
// unbuffered channel(送受信が同期する)
ch := make(chan int)
go func() {
ch <- 42 // 受信側が受け取るまでブロック
}()
val := <-ch // 送信側が送るまでブロック
fmt.Println(val) // 42
// buffered channel(バッファが埋まるまで送信側はブロックしない)
buf := make(chan string, 3)
buf <- "a"
buf <- "b"
buf <- "c"
// buf <- "d" // バッファが満杯なのでブロックする
fmt.Println(<-buf) // "a"
channel の方向を型で制限することで、意図しない操作を防げます。
// chan<- int : 送信専用
func producer(out chan<- int) {
for i := 0; i < 5; i++ {
out <- i
}
close(out) // 送信完了を通知
}
// <-chan int : 受信専用
func consumer(in <-chan int) {
for v := range in { // close されるまでループ
fmt.Println("received:", v)
}
}
func main() {
ch := make(chan int, 5)
go producer(ch)
consumer(ch)
}
複数の goroutine がすべて終了するのを待つには sync.WaitGroup を使います。
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1) // カウンタをインクリメント
go func(id int) {
defer wg.Done() // 終了時にカウンタをデクリメント
fmt.Printf("worker %d done\n", id)
}(i)
}
wg.Wait() // すべての goroutine が Done() を呼ぶまで待機
fmt.Println("all workers done")
}
select は複数の channel 操作を待ち、最初に準備ができたものを実行します。
タイムアウトやキャンセルの実装に欠かせません。
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string, 1)
ch2 := make(chan string, 1)
go func() {
time.Sleep(50 * time.Millisecond)
ch1 <- "ch1 ready"
}()
go func() {
time.Sleep(30 * time.Millisecond)
ch2 <- "ch2 ready"
}()
for i := 0; i < 2; i++ {
select {
case msg := <-ch1:
fmt.Println(msg)
case msg := <-ch2:
fmt.Println(msg)
case <-time.After(100 * time.Millisecond):
fmt.Println("timeout")
}
}
}
// 出力:ch2 ready → ch1 ready(先に受信できた方が先に処理される)
context パッケージは goroutine のキャンセルやデッドラインを伝播させる標準的な仕組みです。
HTTP ハンドラーや外部 API 呼び出しでは必ず使います。
import (
"context"
"fmt"
"time"
)
func slowWork(ctx context.Context) error {
select {
case <-time.After(2 * time.Second):
return nil // 正常終了
case <-ctx.Done():
return ctx.Err() // キャンセル or タイムアウト
}
}
func main() {
// 1秒のタイムアウト
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
if err := slowWork(ctx); err != nil {
fmt.Println("error:", err) // context deadline exceeded
}
}
goroutine を無制限に起動すると過負荷になります。 worker pool は goroutine の数を固定してタスクを並列処理するパターンです。
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
// 重い処理のシミュレーション
result := job * job
fmt.Printf("worker %d: %d -> %d\n", id, job, result)
results <- result
}
}
func main() {
const numWorkers = 3
const numJobs = 10
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
for i := 1; i <= numWorkers; i++ {
wg.Add(1)
go worker(i, jobs, results, &wg)
}
// ジョブを投入
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// すべての worker が終わったら results を close
go func() {
wg.Wait()
close(results)
}()
// 結果を収集
var total int
for r := range results {
total += r
}
fmt.Println("total:", total)
}
共有変数へ複数の goroutine から同時にアクセスする場合は sync.Mutex で保護します。
import "sync"
type SafeCounter struct {
mu sync.Mutex
count int
}
func (c *SafeCounter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
func (c *SafeCounter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}
func main() {
counter := &SafeCounter{}
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Inc()
}()
}
wg.Wait()
fmt.Println(counter.Value()) // 必ず 1000
}
Go の race detector を使うと、データ競合を実行時に検出できます。
# -race フラグでビルド・テスト go run -race main.go go test -race ./...
読み取り専用のアクセスが多い場合は sync.RWMutex の RLock/RUnlock を使うと
パフォーマンスが向上します。
goroutine を活用した高性能 Go アプリも、本番公開には SSL証明書による HTTPS 化が必須です。
エスロジカルではデジサート・サイバートラストの正規取扱代理店として、
2009年から16年以上、RapidSSL(ラピッドSSL) 4,356円/1年(税込)〜でSSL証明書を販売しています。審査サポート・インストール代行も対応しています。
SSL証明書の購入はこちら / Go 言語 Web アプリ入門 / Go Gin REST API 開発入門