Go 1.24におけるWeak Pointerの理解
Lukas Schneider
DevOps Engineer · Leapcell

Goでは、weak pointerは、ガベージコレクタ(GC)が対象オブジェクトを再利用するのを妨げない参照を指します。
オブジェクトがweak pointerによってのみ参照され、強い参照がない場合、GCはそれを到達不能として扱い、再利用します。その後、それへのすべてのweak pointerは自動的にnil
になります。
簡単に言うと、weak pointerはオブジェクトの参照カウントを増やしません。オブジェクトがweak pointerによってのみ参照される場合、ガベージコレクタはそれを解放できます。したがって、weak pointerの値を使用する前に、それがnil
であるかどうかを確認する必要があります。
Go 1.24のweak
パッケージ
Go 1.24では、weak pointerの作成と使用のための簡潔なAPIを提供するweak
パッケージが導入されました。
import "weak" type MyStruct struct { Data string } func main() { obj := &MyStruct{Data: "example"} wp := weak.Make(obj) // weak pointerを作成 val := wp.Value() // 強い参照またはnilを取得 if val != nil { fmt.Println(val.Data) } else { fmt.Println("オブジェクトはガベージコレクションされました") } }
上記の例では、weak.Make(obj)
はobj
へのweak pointerを作成します。
wp.Value()
を呼び出すと、オブジェクトがまだ有効な場合は強い参照が返され、そうでない場合はnil
が返されます。
Weak Pointerのテスト
import ( "fmt" "runtime" "weak" ) type MyStruct struct { Data string } func main() { obj := &MyStruct{Data: "test"} wp := weak.Make(obj) obj = nil // 強い参照を削除 runtime.GC() if wp.Value() == nil { fmt.Println("オブジェクトはガベージコレクションされました") } else { fmt.Println("オブジェクトはまだ有効です") } }
強い参照obj
をnil
に設定し、明示的にGCをトリガーすることで、
オブジェクトがコレクションされると、weak pointerがnil
を返すことがわかります。
Weak Pointerと強い参照の違い
ガベージコレクション(GC)への影響:
- 強い参照はオブジェクトを有効に保ちます。
- Weak Pointerはオブジェクトを有効に保ちません。
Nil値:
- 強い参照は、明示的に何もないものを指している場合に
nil
になります。 - Weak Pointerは、通常、ターゲットオブジェクトがすでにコレクションされているか、まだ割り当てられていないために
nil
になります。
アクセス方法:
- 強い参照は、オブジェクトを直接逆参照できます。
- Weak Pointerは、オブジェクトにアクセスする前に
Value()
メソッドを呼び出す必要があります。
例1:一時的なキャッシュにWeak Pointerを使用する
Weak Pointerの典型的なシナリオは、GCによってコレクションされるのを防ぐことなく、キャッシュにエントリを格納することです。
package main import ( "fmt" "runtime" "sync" "weak" ) type User struct { Name string } var cache sync.Map // map[int]weak.Pointer[*User] func GetUser(id int) *User { // ① 最初にキャッシュからフェッチを試みます if wp, ok := cache.Load(id); ok { if u := wp.(weak.Pointer[User]).Value(); u != nil { fmt.Println("キャッシュヒット") return u } } // ② 実際にロードします(ここでは、単に構築します) u := &User{Name: fmt.Sprintf("user-%d", id)} cache.Store(id, weak.Make(u)) fmt.Println("DBからロード") return u } func main() { u := GetUser(1) // DBからロード fmt.Println(u.Name) runtime.GC() // GCがすぐに実行されても、`main`は強い参照を保持しているため、Userは存続します u = nil // 最後の強い参照を解放 runtime.GC() // GCをトリガーします。Userがコレクションされる可能性があります _ = GetUser(1) // コレクションされた場合は、DBから再度ロードされます }
このキャッシュ実装では、エントリはweak pointerとして格納されます。オブジェクトに他の強い参照がない場合、GCはそれを再利用できます。GetUser
を次に呼び出すと、データがリロードされます。
上記のコードを実行すると、次の出力が生成されます。
$ go run cache.go DBからロード user-1 DBからロード
Weak Pointerを使用する理由
一般的なシナリオは次のとおりです。
- キャッシュ: メモリに保持することを強制せずにオブジェクトを格納します。他の場所で使用されていない場合は、コレクションできます。
- オブザーバーパターン: コレクションされるのを防ぐことなく、オブザーバーへの参照を保持します。
- 正準化: 同じオブジェクトのインスタンスが1つだけ存在するようにし、未使用のインスタンスをコレクションできるようにします。
- 依存関係グラフ: ツリーやグラフなどの構造での参照サイクルを防ぎます。
Weak Pointerの使用に関する注意点
- 常にnilを確認する: オブジェクトはGCサイクルのいつでもコレクションされる可能性があります。
Value()
の結果はキャッシュしないでください。 - 循環依存関係を回避する: Weak Pointerによって参照されるオブジェクトがコンテナへの強い参照を保持しないようにしてください。そうしないと、サイクルが形成される可能性があります。
- パフォーマンスのトレードオフ: Weak Pointerへのアクセスには追加の呼び出しが必要であり、
nil
に戻るオブジェクトを頻繁にリロードすると、チャーンが発生する可能性があります。
例2:強いポインタの通常の使用
package main import ( "fmt" "runtime" ) type Session struct { ID string } func main() { s := new(Session) // &Session{}と同等 s.ID = "abc123" fmt.Println("強い参照は有効です:", s.ID) s = nil // 最後の強い参照を削除 runtime.GC() // GCをトリガーしようとします(デモのみ、実際のタイミングはランタイムによって異なります) fmt.Println("完了") }
ここでは、s
は強いポインタです。それが到達可能なままである限り、Session
オブジェクトはGCによってコレクションされることはありません。
強いポインタが指すオブジェクトがコレクションされるのはいつですか?
-
到達可能性分析: GoはマークアンドスイープGCを使用します。GCサイクルが開始されると、ランタイムはルートオブジェクト(スタック、グローバル変数、現在のレジスタなど)からのすべての強い参照をトラバースします。
- 強い参照を介して到達可能なオブジェクトは_到達可能_と見なされ、有効なままになります。
- その他はすべて_到達不能_とマークされ、スイープフェーズ中に解放されます。
-
参照カウントなし: Goは、オブジェクトがルートから到達可能かどうかのみをチェックします。参照の数や値が等しいかどうかは、コレクションに影響しません。
-
不確かなタイミング: GCサイクルは、スケジューラによって自動的にトリガーされます。開発者は
runtime.GC()
を_ヒント_として呼び出すことができますが、これにより即時のコレクションが保証されるわけではありません。 -
変数自体がコレクションされる可能性があります: 強いポインタ変数
s
がヒープに存在し、それを保持する構造が到達不能になった場合、s
自体がコレクションされる可能性があります。スタック変数は、関数が戻るときに解放されます。
まとめ
強いポインタは、それが指すオブジェクトがすべてのGCサイクルで「ライブセット」に残ることを保証します。最後の強い参照が壊れると、オブジェクトは次のGCサイクル中に自動的に解放されます。
Leapcellは、Goプロジェクトのホスティングに最適な選択肢です。
Leapcellは、Webホスティング、非同期タスク、およびRedis向けの次世代サーバーレスプラットフォームです。
多言語サポート
- Node.js、Python、Go、またはRustで開発します。
無制限のプロジェクトを無料でデプロイ
- 使用量に対してのみ料金を支払います。リクエストも料金もかかりません。
比類のないコスト効率
- アイドル料金なしの従量課金制。
- 例:$25で、平均応答時間60msで694万リクエストをサポートします。
合理化された開発者エクスペリエンス
- 簡単なセットアップのための直感的なUI。
- 完全に自動化されたCI/CDパイプラインとGitOps統合。
- 実用的な洞察のためのリアルタイムメトリクスとロギング。
簡単なスケーラビリティと高性能
- 簡単な同時実行を処理するための自動スケーリング。
- 運用上のオーバーヘッドはゼロです。構築に集中してください。
ドキュメントで詳細をご覧ください。
Xでフォローしてください:@LeapcellHQ