固有の Go メモリ モデル Happen-Before
複数のゴルーチンが同じデータに同時にアクセスする場合、同時アクセス操作はシリアル化する必要があります。 Go での読み取りと書き込みのシリアル化は、チャネル通信またはその他の同期プリミティブ (ミューテックス ロック、同期パッケージの読み取り/書き込みロック、sync/atomic のアトミック操作など) を通じて保証できます。
Happens Before
単一の goroutine では、読み取りと書き込みの動作が、プログラムで指定された実行順序と一致している必要があります。言い換えれば、コンパイラとプロセッサは、言語仕様で定義された動作を変更することなく、単一のゴルーチン内の命令を並べ替えることができます。
a := 1 b := 2
命令の並べ替えにより、b := 2
が a := 1
より前に実行される可能性があります。単一のゴルーチンでは、実行順序を調整しても最終結果には影響しません。ただし、複数の goroutine シナリオでは問題が発生する可能性があります。
var a, b int // goroutine A go func() { a := 5 b := 1 }() // goroutine B go func() { for b == 1 {} fmt.Println(a) }()
上記のコードを実行すると、ゴルーチン B は通常 5 を出力するはずですが、命令の並べ替えにより、b := 1
が a := 5# より前に実行される可能性があります。 ## 、最終的には goroutine B が 0 を出力する可能性があります。
”注: 上記の例は間違った例であり、説明のみを目的としています。
”
読み取りおよび書き込み操作の要件を明確にするために、Go では、メモリ操作を実行するための部分的な順序関係を表す happens before
を導入しました。
happens-before の役割
複数のゴルーチンが共有変数にアクセスする場合、同期イベントを確立して、happens-before 条件が確実に実行されるようにする必要があります。これにより、読み取りで予期された書き込みが確実に行われるようになります。
前に何が起こるか##
#イベント e1 がイベント e2 より前に発生する場合、e2 は e1 の後に発生すると言います。同様に、e1 が e2 の前にも e2 の後にも発生しない場合、e1 と e2 は同時に発生すると言います。 単一の goroutine では、happens-before の順序がプログラムの実行順序になります。では、happens-before の順序は何でしょうか?以下に条件を見てみましょう。 変数 v に対する読み取り操作 r と書き込み操作 w が次の 2 つの条件を満たす場合、r はに w の観察を許可します:
# r は w の前には現れません。- w の後および r の前には、他の書き込み操作は発生しません。
- 変数 v の読み取り操作 r が特定の書き込み操作 w を確実に監視できるようにするには、r によって監視できる書き込み操作が w だけであることを確認する必要があります。 。次に、r と w の両方が次の条件を満たす場合、r は
w が r の前に出現することを保証します。
- 他の書き込み操作は、w の前と r の後に発生します。
- 単一の goroutine には同時実行性はありません。これら 2 つの条件は同等です。 Lao Xu はこれをベースに拡張し、これら 2 つの条件セットがシングルコア動作環境では同等であることを発見しました。同時実行性の場合、後者の条件セットは最初の条件セットよりも厳格です。
迷っているなら、それは正解です。老徐も最初は混乱していましたが、この 2 つの条件は同じでした。このため、老許は特別に原文と繰り返し比較し、上記の理解が正しいことを確認しました。
考え方を変えて、逆推論をしてみましょう。 2 つの条件が同じであれば、元のテキストを 2 回書く必要はありませんが、当然のことながら、問題は単純ではありません。
分析を続ける前に、中国語の先生に感謝したいと思います。先生なしでは、両者の違いを見つけることはできなかったでしょう。
が存在することを示します)。同時に発生する可能性があります)。r が w
より前に発生しない場合、r が発生する可能性がある状況は、以下の図に示すように、r が w の後、または w と同時に発生することです (実線は、w
の前に他の書き込み操作は発生しません。その後、他の書き込み w' が w の前または w と同時に発生する可能性があります。以下の図に示すように、r の後に、または r と同時に発生する可能性があります (実線は同時に発生する可能性があることを示します)。 2 番目の条件セットは非常に明確です。以下に示すように、w は r の前に発生し、他の書き込み操作は w の前か r の後にのみ発生します (空白は、w が r の前に発生することを示します)。同時に実行することはできません)。 #この時点で、2 番目の条件セットが最初の条件セットよりも厳しい理由を理解できるはずです。最初の条件セットでは w の観測が許可され、2 番目の条件セットでは w の観測が保証されます。w の後および r
Go での同期
以下は、Go で合意されたいくつかの同期イベントです。これにより、プログラムが確実に前発生の原則に従うようになります。したがって、並行ゴルーチンを比較的規則的に作成します。 ###Go的初始化
程序初始化运行在单个goroutine中,但是该goroutine可以创建其他并发运行的goroutine。
如果包p导入了包q,则q包init函数执行结束先于p包init函数的执行。main函数的执行发生在所有init函数执行完成之后。
goroutine的创建结束
goroutine的创建先于goroutine的执行。老许觉得这基本就是废话,但事情总是没有那么简单,其隐含之意大概是goroutine的创建是阻塞的。
func sleep() bool { time.Sleep(time.Second) return true } go fmt.Println(sleep())
ログイン後にコピー上述代码会阻塞主goroutine一秒,然后才创建子goroutine。
goroutine的退出是无法预测的。如果用一个goroutine观察另一个goroutine,请使用锁或者Channel来保证相对有序。
Channel的发送和接收
Channel通信是goroutine之间同步的主要方式。
Channel的发送动作先于相应的接受动作完成之前。
无缓冲Channel的接受先于该Channel上的发送完成之前。
这两点总结起来分别是
开始发送
、开始接受
、发送完成
和接受完成
四个动作,其时序关系如下。开始发送 > 接受完成 开始接受 > 发送完成
ログイン後にコピー“
注意:开始发送和开始接受并无明确的先后关系
”Channel的关闭发生在由于通道关闭而返回零值接受之前。
容量为C的Channel第k个接受先于该Channel上的第k+C个发送完成之前。
这里使用极限法应该更加易于理解,如果C为0,k为1则其含义和无缓冲Channel的一致。
Lock
对于任何sync.Mutex或sync.RWMutex变量l以及n < m,第n次l.Unlock()的调用先于第m次l.Lock()的调用返回。
假设n为1,m为2,则第二次调用l.Lock()返回前一定要先调用l.UnLock()。
对于sync.RWMutex的变量l存在这样一个n,使得l.RLock()的调用返回在第n次l.Unlock()之后发生,而与之匹配的l.RUnlock()发生在第n + 1次l.Lock()之前。
不得不说,上面这句话简直不是人能理解的。老许将其翻译成人话:
有写锁时:l.RLock()的调用返回发生在l.Unlock()之后。
有读锁时:l.RUnlock()的调用发生在l.Lock()之前。
“
注意:调用l.RUnlock()前不调用l.RLock()和调用l.Unlock()前不调用l.Lock()会引起panic。
”Once
once.Do(f)中f的返回先于任意其他once.Do的返回。
不正确的同步
错误示范一
var a, b int func f() { a = 1 b = 2 } func g() { print(b) print(a) } func main() { go f() g() }
ログイン後にコピー这个例子看起来挺简单,但是老许相信大部分人应该会忽略指令重排序引起的异常输出。假如goroutine f指令重排序后,
b=2
先于a=1
发生,此时主goroutine观察到b发生变化而未观察到a变化,因此有可能输出20
。“
老许在本地实验了多次结果都是输出
”00
,20
这个输出估计只活在理论之中了。错误示范二
var a string var done bool func setup() { a = "hello, world" done = true } func doprint() { if !done { once.Do(setup) } print(a) } func twoprint() { go doprint() go doprint() }
ログイン後にコピー这种双重检测本意是为了避免同步的开销,但是依旧有可能打印出空字符串而不是“hello, world”。说实话老许自己都不敢保证以前没有写过这样的代码。现在唯一能想到的场景就是其中一个goroutine doprint执行到
done = true
(指令重排序导致done=true
先于a="hello, world"
执行)时,另一个goroutine doprint刚开始执行并观察到done的值为true从而打印空字符串。
以上が固有の Go メモリ モデル Happen-Beforeの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック









Go では、関数のライフ サイクルには定義、ロード、リンク、初期化、呼び出し、戻り値が含まれます。変数のスコープは関数レベルとブロック レベルに分割されますが、ブロック内の変数はブロック内でのみ表示されます。 。

Go では、正規表現を使用してタイムスタンプを照合できます。ISO8601 タイムスタンプの照合に使用されるような正規表現文字列をコンパイルします。 ^\d{4}-\d{2}-\d{2}T \d{ 2}:\d{2}:\d{2}(\.\d+)?(Z|[+-][0-9]{2}:[0-9]{2})$ 。 regexp.MatchString 関数を使用して、文字列が正規表現と一致するかどうかを確認します。

Go では、gorilla/websocket パッケージを使用して WebSocket メッセージを送信できます。具体的な手順: WebSocket 接続を確立します。テキスト メッセージを送信します。 WriteMessage(websocket.TextMessage,[]byte("message")) を呼び出します。バイナリ メッセージを送信します。WriteMessage(websocket.BinaryMessage,[]byte{1,2,3}) を呼び出します。

Go と Go 言語は、異なる特性を持つ別個の存在です。 Go (Golang とも呼ばれます) は、同時実行性、高速なコンパイル速度、メモリ管理、およびクロスプラットフォームの利点で知られています。 Go 言語の欠点としては、他の言語に比べてエコシステムが充実していないこと、構文が厳格であること、動的型付けが欠如していることが挙げられます。

メモリ リークは、ファイル、ネットワーク接続、データベース接続などの使用されなくなったリソースを閉じることによって、Go プログラムのメモリを継続的に増加させる可能性があります。弱参照を使用してメモリ リークを防ぎ、強参照されなくなったオブジェクトをガベージ コレクションの対象にします。 go coroutine を使用すると、メモリ リークを避けるために、終了時にコルーチンのスタック メモリが自動的に解放されます。

Golang では、エラー ラッパーを使用して、元のエラーにコンテキスト情報を追加することで新しいエラーを作成できます。これを使用すると、さまざまなライブラリまたはコンポーネントによってスローされるエラーの種類を統一し、デバッグとエラー処理を簡素化できます。手順は次のとおりです。errors.Wrap 関数を使用して、元のエラーを新しいエラーにラップします。新しいエラーには、元のエラーのコンテキスト情報が含まれています。 fmt.Printf を使用してラップされたエラーを出力し、より多くのコンテキストとアクション性を提供します。異なる種類のエラーを処理する場合は、errors.Wrap 関数を使用してエラーの種類を統一します。

並行関数の単体テストは、同時環境での正しい動作を確認するのに役立つため、非常に重要です。同時実行機能をテストするときは、相互排他、同期、分離などの基本原則を考慮する必要があります。並行機能は、シミュレーション、競合状態のテスト、および結果の検証によって単体テストできます。

Go 言語で優先度のゴルーチンを作成するには、カスタム ゴルーチン作成関数の登録 (ステップ 1) と優先度の値の指定 (ステップ 2) の 2 つの手順があります。このようにして、異なる優先度を持つゴルーチンを作成し、リソース割り当てを最適化し、実行効率を向上させることができます。
