Leapcell: 最適なGolangアプリホスティングのサーバレスプラットフォーム
1. Contextとは何か?
簡単に言えば、ContextはGo 1.7バージョンで導入された標準ライブラリのインターフェースです。その定義は以下の通りです。
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
このインターフェースは4つのメソッドを定義しています。
-
Deadline:
context.Context
がキャンセルされる時刻、つまり期限を設定します。 -
Done:読み取り専用のチャネルを返します。Contextがキャンセルされるか、期限が到来すると、このチャネルがクローズされ、Contextのチェーンの終了を示します。
Done
メソッドを複数回呼び出すと、同じチャネルが返されます。 -
Err:
context.Context
の終了理由を返します。Done
によって返されるチャネルがクローズされたときにのみ、非nullの値を返します。返り値には2つのケースがあります。-
context.Context
がキャンセルされた場合、Canceled
を返します。 -
context.Context
がタイムアウトした場合、DeadlineExceeded
を返します。
-
-
Value:
context.Context
からキーに対応する値を取得します。これはマップのget
メソッドと似ています。同じコンテキストで同じキーを使ってValue
を複数回呼び出すと、同じ結果が返されます。対応するキーがない場合は、nil
を返します。キー - 値のペアはWithValue
メソッドを通じて書き込まれます。
2. Contextの作成
2.1 ルートコンテキストの作成
ルートコンテキストを作成する主な方法は主に2つあります。
context.Background()
context.TODO()
ソースコードを分析すると、context.Background
とcontext.TODO
の間に大きな違いはありません。どちらもルートコンテキストを作成するために使用され、機能を持たない空のコンテキストです。ただし、一般的に、現在の関数にコンテキストが入力パラメータとして存在しない場合、通常はcontext.Background
を使用してルートコンテキストを作成し、開始コンテキストとして下に渡します。
2.2 子コンテキストの作成
ルートコンテキストが作成された後、それ自体には機能はありません。コンテキストをプログラムで役立たせるには、context
パッケージが提供するWith
シリーズの関数を使って派生させます。
主に以下の派生関数があります。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
現在のコンテキストに基づいて、各With
関数は新しいコンテキストを作成します。これは私たちがよく知る木構造に似ています。現在のコンテキストを親コンテキストと呼び、新しく派生したコンテキストを子コンテキストと呼びます。ルートコンテキストを元に、4つのWith
シリーズのメソッドを使って4種類のコンテキストを派生させることができます。各コンテキストは同じ方法でWith
シリーズのメソッドを呼び出すことで、新しい子コンテキストを引き続き派生させることができ、全体の構造が木のように見えます。
3. Contextの用途は何か?
Contextは主に2つの用途があり、これらはまたプロジェクトでも一般的に使用されます。
- 並行制御のため、goroutineを優雅に終了させるため。
- コンテキスト情報を渡すため。
一般的に、Contextは親と子のgoroutine間で値を渡し、キャンセル信号を送信するメカニズムです。
3.1 並行制御
典型的なサーバーは継続的に実行され、クライアントやブラウザからの要求を受け取り、応答するように待機しています。このシナリオを考えてみましょう:バックエンドのマイクロサービスアーキテクチャで、サーバーが要求を受け取った場合、ロジックが複雑な場合は、単一のgoroutineでタスクを完了することはできません。代わりに、多くのgoroutineを作成して一緒に要求を処理します。要求が入力されると、まずRPC1呼び出しを経て、次にRPC2に進み、さらに2つのRPCが作成されて実行されます。RPC4の中には別のRPC呼び出し(RPC5)があります。すべてのRPC呼び出しが成功すると結果が返されます。この一連の呼び出しの過程で、もしRPC1でエラーが発生したとしましょう。コンテキストがない場合、すべてのRPCが終了するまで待たなければならず、実際には多くの時間が浪費されます。なぜなら、エラーが発生するとすぐに、RPC1で結果を返し、後続のRPCが完了するのを待つ必要がないからです。もしRPC1で失敗を直接返し、後続のRPCが続行するのを待たない場合、後続のRPCの実行は実際には意味がなく、単に計算資源とI/O資源を浪費するだけです。コンテキストを導入することで、この問題をうまく処理することができます。子goroutineがもはや必要ない場合、コンテキストを通じてそれらに優雅に終了するよう通知することができます。
3.1.1 context.WithCancel
このメソッドは以下のように定義されています。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
context.WithCancel
関数はキャンセル制御関数です。コンテキストを1つのパラメータとして取り、context.Context
から新しい子コンテキストとキャンセル関数CancelFunc
を派生させることができます。この子コンテキストを新しいgoroutineに渡すことで、これらのgoroutineの終了を制御することができます。返されたキャンセル関数CancelFunc
を実行すると、現在のコンテキストとその子コンテキストがキャンセルされ、すべてのgoroutineがキャンセル信号を同期的に受け取ります。
使用例:
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
go Watch(ctx, "goroutine1")
go Watch(ctx, "goroutine2")
time.Sleep(6 * time.Second) // goroutine1とgoroutine2を6秒間実行させる
fmt.Println("end working!!!")
cancel() // goroutine1とgoroutine2に終了を通知する
time.Sleep(1 * time.Second)
}
func Watch(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Printf("%s exit!\n", name) // メインのgoroutineがcancelを呼び出した後、ctx.Done()チャネルに信号が送信され、この部分がメッセージを受け取ります
return
default:
fmt.Printf("%s working...\n", name)
time.Sleep(time.Second)
}
}
}
実行結果:
goroutine2 working...
goroutine1 working...
goroutine1 working...
goroutine2 working...
goroutine2 working...
goroutine1 working...
goroutine1 working...
goroutine2 working...
goroutine2 working...
goroutine1 working...
goroutine1 working...
goroutine2 working...
end working!!!
goroutine1 exit!
goroutine2 exit!
ctx, cancel := context.WithCancel(context.Background())
は、戻り関数cancel
を持つctx
を派生させ、子goroutineに渡します。次の6秒間は、cancel
関数が実行されないため、子goroutineは常にdefault
文を実行し、監視情報を印刷します。6秒後にcancel
が呼び出されます。この時、子goroutineはctx.Done()
チャネルからメッセージを受け取り、return
を実行して終了します。
3.1.2 context.WithDeadline
このメソッドは以下のように定義されています。
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
context.WithDeadline
もキャンセル制御関数です。このメソッドは2つのパラメータを持ちます。最初のパラメータはコンテキストで、2番目のパラメータは期限です。また、子コンテキストとキャンセル関数CancelFunc
を返します。使用する際、期限までに、手動でCancelFunc
を呼び出して子コンテキストをキャンセルし、子goroutineの終了を制御することができます。期限までにCancelFunc
を呼び出さなかった場合、子コンテキストのDone()
チャネルにもキャンセル信号が届き、子goroutineの終了を制御します。
使用例:
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(4*time.Second)) // 現在時刻から4秒後のタイムアウトを設定
defer cancel()
go Watch(ctx, "goroutine1")
go Watch(ctx, "goroutine2")
time.Sleep(6 * time.Second) // goroutine1とgoroutine2を6秒間実行させる
fmt.Println("end working!!!")
}
func Watch(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Printf("%s exit!\n", name) // 4秒後に信号を受け取る
return
default:
fmt.Printf("%s working...\n", name)
time.Sleep(time.Second)
}
}
}
実行結果:
goroutine1 working...
goroutine2 working...
goroutine2 working...
goroutine1 working...
goroutine1 working...
goroutine2 working...
goroutine1 exit!
goroutine2 exit!
end working!!!
cancel
関数を呼び出していませんが、4秒後に子goroutine内のctx.Done()
が信号を受け取り、exit
を印刷し、子goroutineが終了しました。これがWithDeadline
を使って子コンテキストを派生させる方法です。
3.1.3 context.WithTimeout
このメソッドは以下のように定義されています。
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
context.WithTimeout
は機能的にcontext.WithDeadline
と似ています。どちらもタイムアウトによって子コンテキストをキャンセルするために使用されます。唯一の違いは渡される2番目のパラメータです。context.WithTimeout
が渡す2番目のパラメータは特定の時刻ではなく、期間です。
使用例:
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
defer cancel()
go Watch(ctx, "goroutine1")
go Watch(ctx, "goroutine2")
time.Sleep(6 * time.Second) // goroutine1とgoroutine2を6秒間実行させる
fmt.Println("end working!!!")
}
func Watch(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Printf("%s exit!\n", name) // メインのgoroutineがcancelを呼び出した後、ctx.Done()チャネルに信号が送信され、この部分がメッセージを受け取ります
return
default:
fmt.Printf("%s working...\n", name)
time.Sleep(time.Second)
}
}
}
実行結果:
goroutine2 working...
goroutine1 working...
goroutine1 working...
goroutine2 working...
goroutine2 working...
goroutine1 working...
goroutine1 working...
goroutine2 working...
goroutine1 exit!
goroutine2 exit!
end working!!!
このプログラムは非常に単純です。基本的には前のcontext.WithDeadline
の例のコードと同じで、ただコンテキストを派生させるメソッドがcontext.WithTimeout
に変更されています。具体的には、2番目のパラメータはもはや特定の時刻ではなく、4秒という特定の期間になっています。実行結果も同じです。
3.1.4 context.WithValue
このメソッドは以下のように定義されています。
func WithValue(parent Context, key, val interface{}) Context
context.WithValue
関数は、親コンテキストから子コンテキストを作成し、値を渡すために使用されます。関数のパラメータは、親コンテキストとキー - 値のペア(key, val)です。コンテキストを返します。プロジェクトでは、このメソッドは一般的に、一意の要求IDやトレースIDなどのコンテキスト情報を渡すために使用され、リンクトレーシングや設定の渡しに役立ちます。
使用例:
package main
import (
"context"
"fmt"
"time"
)
func func1(ctx context.Context) {
fmt.Printf("name is: %s", ctx.Value("name").(string))
}
func main() {
ctx := context.WithValue(context.Background(), "name", "leapcell")
go func1(ctx)
time.Sleep(time.Second)
}
実行結果:
name is: leapcell
Leapcell: 最適なGolangアプリホスティングのサーバレスプラットフォーム
最後に、Golangサービスのデプロイに最適なプラットフォームを1つおすすめします:Leapcell
1. 多言語対応
- JavaScript、Python、Go、またはRustで開発できます。
2. 無制限のプロジェクトを無料でデプロイ
- 使用量に応じて課金 — 要求がなければ料金はかかりません。
3. 圧倒的なコスト効率
- 使った分だけ課金で、アイドル時の料金はかかりません。
- 例:25ドルで平均応答時間60msで694万回の要求をサポートできます。
4. ストリームライン化された開発者体験
- 直感的なUIで簡単にセットアップできます。
- 完全自動化されたCI/CDパイプラインとGitOpsの統合。
- アクション可能な洞察のためのリアルタイムメトリックとログ。
5. 簡単なスケーラビリティと高パフォーマンス
- 高い並行処理を簡単に処理できる自動スケーリング。
- オペレーション上のオーバーヘッドはゼロ — 構築に集中できます。
LeapcellのTwitter:https://x.com/LeapcellHQ