{"id":1104,"date":"2025-07-02T18:27:39","date_gmt":"2025-07-02T10:27:39","guid":{"rendered":"https:\/\/beijian99.top\/?p=1104"},"modified":"2025-07-07T17:17:30","modified_gmt":"2025-07-07T09:17:30","slug":"%e6%9c%89%e9%99%90%e7%8a%b6%e6%80%81%e6%9c%ba","status":"publish","type":"post","link":"https:\/\/beijian99.top\/?p=1104","title":{"rendered":"\u6709\u9650\u72b6\u6001\u673a"},"content":{"rendered":"\n<h1 class=\"wp-block-heading\">\u57fa\u4e8e\u6709\u9650\u72b6\u6001\u673a(FSM)\u7684Go\u8bed\u8a00\u6597\u5730\u4e3b\u670d\u52a1\u7aef\u8bbe\u8ba1<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">\u5f15\u8a00\uff1a\u72b6\u6001\u673a\u4e0e\u68cb\u724c\u6e38\u620f\u7684\u5b8c\u7f8e\u5951\u5408<\/h2>\n\n\n\n<p>\u6597\u5730\u4e3b\u4f5c\u4e3a\u4e00\u6b3e\u7ecf\u5178\u7684\u4e09\u4eba\u68cb\u724c\u6e38\u620f\uff0c\u5176\u6e38\u620f\u6d41\u7a0b\u5929\u7136\u5177\u6709\u660e\u786e\u7684\u9636\u6bb5\u5212\u5206\uff1a\u53d1\u724c\u3001\u53eb\u5730\u4e3b\u3001\u51fa\u724c\u3001\u7ed3\u7b97\u7b49\u3002\u8fd9\u79cd\u6e05\u6670\u7684\u9636\u6bb5\u6027\u7279\u5f81\u4e0e\u6709\u9650\u72b6\u6001\u673a(FSM)\u7684\u6982\u5ff5\u9ad8\u5ea6\u5951\u5408\u3002\u672c\u6587\u5c06\u8be6\u7ec6\u4ecb\u7ecd\u5982\u4f55\u4f7f\u7528Go\u8bed\u8a00\u5b9e\u73b0\u4e00\u4e2a\u57fa\u4e8eFSM\u7684\u6597\u5730\u4e3b\u6e38\u620f\u670d\u52a1\u7aef\uff0c\u6db5\u76d6\u72b6\u6001\u8bbe\u8ba1\u3001\u7f51\u7edc\u901a\u4fe1\u3001\u5e76\u53d1\u63a7\u5236\u7b49\u6838\u5fc3\u5185\u5bb9\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u4e00\u3001\u6597\u5730\u4e3b\u6e38\u620f\u72b6\u6001\u5206\u6790<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1.1 \u6e38\u620f\u6d41\u7a0b\u5206\u89e3<\/h3>\n\n\n\n<p>\u5178\u578b\u7684\u6597\u5730\u4e3b\u6e38\u620f\u5305\u542b\u4ee5\u4e0b\u4e3b\u8981\u72b6\u6001\uff1a<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nstateDiagram\n    &#x5B;*] --&gt; Waiting  \/\/ \u7b49\u5f85\u73a9\u5bb6\u52a0\u5165\n    Waiting --&gt; Dealing  \/\/ \u5168\u90e8\u51c6\u5907\u540e\u53d1\u724c\n    Dealing --&gt; Bidding  \/\/ \u8fdb\u5165\u53eb\u5730\u4e3b\u9636\u6bb5\n    Bidding --&gt; Playing  \/\/ \u786e\u5b9a\u5730\u4e3b\u540e\u5f00\u59cb\u51fa\u724c\n    Playing --&gt; GameOver  \/\/ \u6e38\u620f\u7ed3\u675f\n    GameOver --&gt; Waiting  \/\/ \u91cd\u65b0\u5f00\u59cb\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">1.2 \u72b6\u6001\u8f6c\u79fb\u4e8b\u4ef6<\/h3>\n\n\n\n<p>\u6bcf\u4e2a\u72b6\u6001\u4e4b\u95f4\u7684\u8f6c\u79fb\u7531\u7279\u5b9a\u4e8b\u4ef6\u89e6\u53d1\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u72b6\u6001\u8f6c\u79fb<\/th><th>\u89e6\u53d1\u6761\u4ef6<\/th><\/tr><\/thead><tbody><tr><td>Waiting \u2192 Dealing<\/td><td>\u6240\u6709\u73a9\u5bb6\u51c6\u5907\u5c31\u7eea<\/td><\/tr><tr><td>Dealing \u2192 Bidding<\/td><td>\u53d1\u724c\u5b8c\u6210<\/td><\/tr><tr><td>Bidding \u2192 Playing<\/td><td>\u5730\u4e3b\u786e\u5b9a\u4e14\u52a0\u500d\u5b8c\u6210<\/td><\/tr><tr><td>Playing \u2192 GameOver<\/td><td>\u6709\u73a9\u5bb6\u51fa\u5b8c\u624b\u724c<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">\u4e8c\u3001Go\u8bed\u8a00FSM\u5b9e\u73b0<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">2.1 \u72b6\u6001\u63a5\u53e3\u8bbe\u8ba1<\/h3>\n\n\n\n<p>\u9996\u5148\u5b9a\u4e49\u72b6\u6001\u63a5\u53e3\u548c\u57fa\u7840\u7ed3\u6784\uff1a<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: go; title: ; notranslate\" title=\"\">\n\/\/ game\/state.go\npackage game\n\ntype State interface {\n    Enter(room *Room)       \/\/ \u8fdb\u5165\u72b6\u6001\n    Exit(room *Room)        \/\/ \u9000\u51fa\u72b6\u6001\n    HandleMessage(room *Room, player *Player, msg *Message) \/\/ \u5904\u7406\u6d88\u606f\n    Tick(room *Room)        \/\/ \u72b6\u6001\u5fc3\u8df3\n}\n\n\/\/ \u57fa\u7840\u72b6\u6001\u5d4c\u5165\u7ed3\u6784\ntype BaseState struct{}\n\nfunc (s *BaseState) Enter(room *Room)       {}\nfunc (s *BaseState) Exit(room *Room)        {}\nfunc (s *BaseState) HandleMessage(room *Room, player *Player, msg *Message) {}\nfunc (s *BaseState) Tick(room *Room)        {}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">2.2 \u5177\u4f53\u72b6\u6001\u5b9e\u73b0<\/h3>\n\n\n\n<p>\u200b<strong>\u200b\u7b49\u5f85\u72b6\u6001\u793a\u4f8b\u200b<\/strong>\u200b\uff1a<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: go; title: ; notranslate\" title=\"\">\n\/\/ game\/waiting_state.go\npackage game\n\ntype WaitingState struct {\n    BaseState\n    readyPlayers map&#x5B;int]bool \/\/ \u8bb0\u5f55\u51c6\u5907\u73a9\u5bb6\n}\n\nfunc NewWaitingState() *WaitingState {\n    return &amp;WaitingState{\n        readyPlayers: make(map&#x5B;int]bool),\n    }\n}\n\nfunc (s *WaitingState) Enter(room *Room) {\n    room.Broadcast(&amp;Message{\n        Type:    &quot;STATE_WAITING&quot;,\n        Content: &quot;\u7b49\u5f85\u5176\u4ed6\u73a9\u5bb6\u51c6\u5907...&quot;,\n    })\n}\n\nfunc (s *WaitingState) HandleMessage(room *Room, player *Player, msg *Message) {\n    switch msg.Type {\n    case &quot;PLAYER_READY&quot;:\n        s.readyPlayers&#x5B;player.ID] = true\n        if len(s.readyPlayers) == 3 {\n            room.ChangeState(NewDealingState())\n        }\n    }\n}\n<\/pre><\/div>\n\n\n<p>\u200b<strong>\u200b\u53d1\u724c\u72b6\u6001\u793a\u4f8b\u200b<\/strong>\u200b\uff1a<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: go; title: ; notranslate\" title=\"\">\n\/\/ game\/dealing_state.go\npackage game\n\ntype DealingState struct {\n    BaseState\n    timer *time.Timer\n}\n\nfunc NewDealingState() *DealingState {\n    return &amp;DealingState{}\n}\n\nfunc (s *DealingState) Enter(room *Room) {\n    \/\/ \u6267\u884c\u53d1\u724c\u903b\u8f91\n    room.DealCards()\n    \n    \/\/ \u8bbe\u7f6e\u72b6\u6001\u8d85\u65f6\n    s.timer = time.AfterFunc(5*time.Second, func() {\n        room.ChangeState(NewBiddingState())\n    })\n}\n\nfunc (s *DealingState) Exit(room *Room) {\n    s.timer.Stop()\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">2.3 \u72b6\u6001\u4e0a\u4e0b\u6587\u7ba1\u7406<\/h3>\n\n\n\n<p>\u6e38\u620f\u623f\u95f4\u4f5c\u4e3a\u72b6\u6001\u673a\u7684\u4e0a\u4e0b\u6587\uff1a<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: go; title: ; notranslate\" title=\"\">\n\/\/ game\/room.go\npackage game\n\ntype Room struct {\n    currentState State\n    players      &#x5B;3]*Player\n    stateMutex   sync.Mutex\n    \/\/ ...\u5176\u4ed6\u623f\u95f4\u5c5e\u6027\n}\n\nfunc (r *Room) ChangeState(newState State) {\n    r.stateMutex.Lock()\n    defer r.stateMutex.Unlock()\n    \n    if r.currentState != nil {\n        r.currentState.Exit(r)\n    }\n    \n    r.currentState = newState\n    r.currentState.Enter(r)\n}\n\nfunc (r *Room) HandleMessage(player *Player, msg *Message) {\n    r.stateMutex.Lock()\n    defer r.stateMutex.Unlock()\n    \n    if r.currentState != nil {\n        r.currentState.HandleMessage(r, player, msg)\n    }\n}\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">\u4e09\u3001\u7f51\u7edc\u901a\u4fe1\u8bbe\u8ba1<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">3.1 \u6d88\u606f\u534f\u8bae\u5b9a\u4e49<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: go; title: ; notranslate\" title=\"\">\n\/\/ network\/message.go\npackage network\n\ntype MessageType string\n\nconst (\n    MsgStateChange  MessageType = &quot;STATE_CHANGE&quot;\n    MsgPlayerAction MessageType = &quot;PLAYER_ACTION&quot;\n    \/\/ ...\u5176\u4ed6\u6d88\u606f\u7c7b\u578b\n)\n\ntype Message struct {\n    Type    MessageType   `json:&quot;type&quot;`\n    Content interface{}   `json:&quot;content&quot;`\n    PlayerID int          `json:&quot;player_id,omitempty&quot;`\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">3.2 WebSocket\u96c6\u6210<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: go; title: ; notranslate\" title=\"\">\n\/\/ network\/server.go\npackage network\n\nfunc StartServer() {\n    http.HandleFunc(&quot;\/ws&quot;, func(w http.ResponseWriter, r *http.Request) {\n        conn, err := upgrader.Upgrade(w, r, nil)\n        if err != nil {\n            log.Println(&quot;Upgrade error:&quot;, err)\n            return\n        }\n        \n        go handleConnection(conn)\n    })\n    \n    log.Fatal(http.ListenAndServe(&quot;:8080&quot;, nil))\n}\n\nfunc handleConnection(conn *websocket.Conn) {\n    player := game.NewPlayer()\n    defer conn.Close()\n    \n    for {\n        _, msgBytes, err := conn.ReadMessage()\n        if err != nil {\n            break\n        }\n        \n        var msg Message\n        if err := json.Unmarshal(msgBytes, &amp;msg); err != nil {\n            continue\n        }\n        \n        room := game.GetRoomByPlayer(player.ID)\n        if room != nil {\n            room.HandleMessage(player, &amp;msg)\n        }\n    }\n}\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">\u56db\u3001\u5173\u952e\u6e38\u620f\u903b\u8f91\u5b9e\u73b0<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">4.1 \u53eb\u5730\u4e3b\u72b6\u6001\u5b9e\u73b0<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: go; title: ; notranslate\" title=\"\">\n\/\/ game\/bidding_state.go\npackage game\n\ntype BiddingState struct {\n    BaseState\n    currentBidder int\n    maxBid        int\n    passCount     int\n    timer         *time.Timer\n}\n\nfunc (s *BiddingState) Enter(room *Room) {\n    s.currentBidder = room.GetNextPlayer(-1)\n    s.maxBid = 0\n    s.passCount = 0\n    \n    room.Broadcast(&amp;Message{\n        Type: &quot;BIDDING_START&quot;,\n        Content: map&#x5B;string]interface{}{\n            &quot;currentPlayer&quot;: s.currentBidder,\n            &quot;maxBid&quot;:       s.maxBid,\n        },\n    })\n    \n    s.timer = time.AfterFunc(15*time.Second, s.onBidTimeout)\n}\n\nfunc (s *BiddingState) HandleMessage(room *Room, player *Player, msg *Message) {\n    if msg.Type != &quot;PLAYER_BID&quot; || player.ID != s.currentBidder {\n        return\n    }\n    \n    bid := msg.Content.(int)\n    if bid &gt; s.maxBid &amp;&amp; bid &lt;= 3 {\n        s.maxBid = bid\n        s.passCount = 0\n    } else if bid == 0 { \/\/ 0\u8868\u793a\u4e0d\u53eb\n        s.passCount++\n    }\n    \n    if s.passCount &gt;= 2 &amp;&amp; s.maxBid &gt; 0 {\n        room.SetLandlord(s.currentBidder, s.maxBid)\n        room.ChangeState(NewPlayingState())\n        return\n    }\n    \n    s.currentBidder = room.GetNextPlayer(s.currentBidder)\n    room.BroadcastBidInfo()\n    \n    \/\/ \u91cd\u7f6e\u8ba1\u65f6\u5668\n    s.timer.Reset(15 * time.Second)\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">4.2 \u51fa\u724c\u72b6\u6001\u5b9e\u73b0<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: go; title: ; notranslate\" title=\"\">\n\/\/ game\/playing_state.go\npackage game\n\ntype PlayingState struct {\n    BaseState\n    currentPlayer  int\n    lastCards      &#x5B;]Card\n    lastPlayer     int\n    timer          *time.Timer\n    consecutivePass int\n}\n\nfunc (s *PlayingState) HandleMessage(room *Room, player *Player, msg *Message) {\n    if msg.Type != &quot;PLAY_CARDS&quot; || player.ID != s.currentPlayer {\n        return\n    }\n    \n    cards := parseCards(msg.Content)\n    if !room.IsValidPlay(player.ID, cards, s.lastCards, s.lastPlayer) {\n        player.SendError(&quot;\u51fa\u724c\u4e0d\u5408\u6cd5&quot;)\n        return\n    }\n    \n    if len(cards) &gt; 0 {\n        s.lastCards = cards\n        s.lastPlayer = player.ID\n        s.consecutivePass = 0\n        room.BroadcastPlay(player.ID, cards)\n        \n        if player.CardsLeft() == 0 {\n            room.ChangeState(NewGameOverState(player.ID))\n            return\n        }\n    } else {\n        s.consecutivePass++\n        if s.consecutivePass &gt;= 2 {\n            s.lastCards = nil\n            s.lastPlayer = -1\n        }\n    }\n    \n    s.currentPlayer = room.GetNextPlayer(s.currentPlayer)\n    room.BroadcastTurn(s.currentPlayer)\n    s.timer.Reset(30 * time.Second)\n}\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">\u4e94\u3001\u5e76\u53d1\u63a7\u5236\u4e0e\u6027\u80fd\u4f18\u5316<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">5.1 \u72b6\u6001\u5b89\u5168\u7684\u5e76\u53d1\u8bbf\u95ee<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: go; title: ; notranslate\" title=\"\">\n\/\/ game\/room.go\nfunc (r *Room) processMessage(player *Player, msg *Message) {\n    \/\/ \u4f7f\u7528\u5e26\u7f13\u51b2\u7684\u901a\u9053\u5904\u7406\u6d88\u606f\n    select {\n    case r.messageQueue &lt;- messageWrapper{player, msg}:\n    default:\n        player.SendError(&quot;\u670d\u52a1\u5668\u7e41\u5fd9&quot;)\n    }\n}\n\nfunc (r *Room) startMessageLoop() {\n    go func() {\n        for wrapper := range r.messageQueue {\n            r.HandleMessage(wrapper.player, wrapper.msg)\n        }\n    }()\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">5.2 \u5b9a\u65f6\u5668\u7ba1\u7406\u4f18\u5316<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: go; title: ; notranslate\" title=\"\">\n\/\/ game\/state.go\ntype StateBase struct {\n    timers &#x5B;]*time.Timer\n}\n\nfunc (s *StateBase) AddTimer(d time.Duration, f func()) {\n    timer := time.AfterFunc(d, f)\n    s.timers = append(s.timers, timer)\n}\n\nfunc (s *StateBase) ClearTimers() {\n    for _, t := range s.timers {\n        t.Stop()\n    }\n    s.timers = nil\n}\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">\u516d\u3001\u6d4b\u8bd5\u4e0e\u8c03\u8bd5<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">6.1 \u72b6\u6001\u673a\u5355\u5143\u6d4b\u8bd5<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: go; title: ; notranslate\" title=\"\">\n\/\/ game\/state_test.go\nfunc TestBiddingState(t *testing.T) {\n    room := NewTestRoom()\n    state := NewBiddingState()\n    \n    \/\/ \u6d4b\u8bd5\u6b63\u5e38\u53eb\u5206\u6d41\u7a0b\n    state.Enter(room)\n    state.HandleMessage(room, room.players&#x5B;0], &amp;Message{Type: &quot;PLAYER_BID&quot;, Content: 1})\n    assert.Equal(t, 1, state.maxBid)\n    \n    \/\/ \u6d4b\u8bd5\u8d85\u65f6\u5904\u7406\n    state.onBidTimeout()\n    assert.True(t, room.currentState instanceof.GameOverState)\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">6.2 \u96c6\u6210\u6d4b\u8bd5\u65b9\u6848<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: go; title: ; notranslate\" title=\"\">\nfunc TestFullGameFlow(t *testing.T) {\n    \/\/ \u521b\u5efa\u6d4b\u8bd5\u623f\u95f4\u548c\u73a9\u5bb6\n    room := NewTestRoom()\n    players := &#x5B;]*TestPlayer{NewTestPlayer(), NewTestPlayer(), NewTestPlayer()}\n    \n    \/\/ \u6a21\u62df\u5b8c\u6574\u6e38\u620f\u6d41\u7a0b\n    room.Join(players&#x5B;0])\n    room.Join(players&#x5B;1])\n    room.Join(players&#x5B;2])\n    \n    \/\/ \u6240\u6709\u73a9\u5bb6\u51c6\u5907\n    for _, p := range players {\n        room.HandleMessage(p, &amp;Message{Type: &quot;PLAYER_READY&quot;})\n    }\n    \n    \/\/ \u9a8c\u8bc1\u72b6\u6001\u8f6c\u79fb\n    assert.IsType(t, &amp;DealingState{}, room.currentState)\n    \n    \/\/ ...\u7ee7\u7eed\u6a21\u62df\u540e\u7eed\u6e38\u620f\u6d41\u7a0b\n}\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">\u4e03\u3001\u6269\u5c55\u4e0e\u6f14\u8fdb<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">7.1 \u652f\u6301\u65ad\u7ebf\u91cd\u8fde<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: go; title: ; notranslate\" title=\"\">\n\/\/ game\/playing_state.go\nfunc (s *PlayingState) HandleReconnect(player *Player) {\n    player.Send(&amp;Message{\n        Type: &quot;GAME_SNAPSHOT&quot;,\n        Content: map&#x5B;string]interface{}{\n            &quot;state&quot;:        &quot;PLAYING&quot;,\n            &quot;current&quot;:     s.currentPlayer,\n            &quot;lastCards&quot;:   s.lastCards,\n            &quot;lastPlayer&quot;:  s.lastPlayer,\n            &quot;yourCards&quot;:   player.Cards(),\n        },\n    })\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\">7.2 \u72b6\u6001\u6301\u4e45\u5316<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: go; title: ; notranslate\" title=\"\">\n\/\/ game\/room.go\nfunc (r *Room) SaveState() (*RoomState, error) {\n    state := &amp;RoomState{\n        StateName: reflect.TypeOf(r.currentState).Name(),\n        Players:   r.players,\n        \/\/ \u5176\u4ed6\u9700\u8981\u4fdd\u5b58\u7684\u5c5e\u6027\n    }\n    \n    if savable, ok := r.currentState.(StateSaver); ok {\n        state.StateData = savable.Save()\n    }\n    \n    return state, nil\n}\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">\u603b\u7ed3\uff1aFSM\u5728\u6e38\u620f\u670d\u52a1\u7aef\u4e2d\u7684\u4ef7\u503c<\/h2>\n\n\n\n<p>\u901a\u8fc7\u6709\u9650\u72b6\u6001\u673a\u67b6\u6784\u5b9e\u73b0\u7684\u6597\u5730\u4e3b\u670d\u52a1\u7aef\u5177\u6709\u4ee5\u4e0b\u4f18\u52bf\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u200b<strong>\u200b\u6e05\u6670\u7684\u903b\u8f91\u5212\u5206\u200b<\/strong>\u200b\uff1a\u6bcf\u4e2a\u6e38\u620f\u9636\u6bb5\u90fd\u6709\u660e\u786e\u7684\u72b6\u6001\u5bf9\u5e94<\/li>\n\n\n\n<li>\u200b<strong>\u200b\u6613\u4e8e\u6269\u5c55\u7ef4\u62a4\u200b<\/strong>\u200b\uff1a\u65b0\u589e\u6e38\u620f\u89c4\u5219\u6216\u72b6\u6001\u4e0d\u4f1a\u5f71\u54cd\u73b0\u6709\u903b\u8f91<\/li>\n\n\n\n<li>\u200b<strong>\u200b\u7a33\u5b9a\u7684\u65f6\u5e8f\u63a7\u5236\u200b<\/strong>\u200b\uff1a\u72b6\u6001\u8f6c\u79fb\u673a\u5236\u786e\u4fdd\u6e38\u620f\u6d41\u7a0b\u6b63\u786e\u6027<\/li>\n\n\n\n<li>\u200b<strong>\u200b\u826f\u597d\u7684\u53ef\u6d4b\u8bd5\u6027\u200b<\/strong>\u200b\uff1a\u6bcf\u4e2a\u72b6\u6001\u53ef\u4ee5\u72ec\u7acb\u6d4b\u8bd5<\/li>\n<\/ol>\n\n\n\n<p>Go\u8bed\u8a00\u7684\u5e76\u53d1\u7279\u6027\u4e0eFSM\u6a21\u5f0f\u76f8\u5f97\u76ca\u5f70\uff0c\u901a\u8fc7goroutine\u548cchannel\u53ef\u4ee5\u4f18\u96c5\u5730\u5904\u7406\u6e38\u620f\u4e2d\u7684\u5f02\u6b65\u4e8b\u4ef6\u3002\u8fd9\u79cd\u67b6\u6784\u4e0d\u4ec5\u9002\u7528\u4e8e\u6597\u5730\u4e3b\uff0c\u4e5f\u53ef\u4ee5\u63a8\u5e7f\u5230\u5176\u4ed6\u68cb\u724c\u7c7b\u6e38\u620f\u751a\u81f3\u66f4\u590d\u6742\u7684\u6e38\u620f\u7cfb\u7edf\u4e2d\u3002<\/p>\n\n\n\n<p>\u968f\u7740\u6e38\u620f\u903b\u8f91\u590d\u6742\u5ea6\u7684\u589e\u52a0\uff0c\u53ef\u4ee5\u8003\u8651\u5728FSM\u57fa\u7840\u4e0a\u5f15\u5165\u5206\u5c42\u72b6\u6001\u673a\u3001\u884c\u4e3a\u6811\u7b49\u66f4\u9ad8\u7ea7\u7684\u6280\u672f\uff0c\u4f46FSM\u59cb\u7ec8\u662f\u6e38\u620f\u670d\u52a1\u7aef\u5f00\u53d1\u4e2d\u6700\u57fa\u7840\u3001\u6700\u53ef\u9760\u7684\u8bbe\u8ba1\u6a21\u5f0f\u4e4b\u4e00\u3002<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"474\" height=\"268\" src=\"https:\/\/beijian99.top\/wp-content\/uploads\/2025\/07\/image-10.png\" alt=\"\" class=\"wp-image-1128\" srcset=\"https:\/\/beijian99.top\/wp-content\/uploads\/2025\/07\/image-10.png 474w, https:\/\/beijian99.top\/wp-content\/uploads\/2025\/07\/image-10-300x170.png 300w\" sizes=\"auto, (max-width: 474px) 100vw, 474px\" \/><\/figure>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u57fa\u4e8e\u6709\u9650\u72b6\u6001\u673a(FSM)\u7684Go\u8bed\u8a00\u6597\u5730\u4e3b\u670d\u52a1\u7aef\u8bbe\u8ba1 \u5f15\u8a00\uff1a\u72b6\u6001\u673a\u4e0e\u68cb\u724c\u6e38\u620f\u7684\u5b8c\u7f8e\u5951\u5408 \u6597\u5730\u4e3b\u4f5c\u4e3a\u4e00\u6b3e\u7ecf\u5178\u7684\u4e09\u4eba [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1128,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[4],"tags":[],"class_list":["post-1104","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-algorithm"],"_links":{"self":[{"href":"https:\/\/beijian99.top\/index.php?rest_route=\/wp\/v2\/posts\/1104","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/beijian99.top\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/beijian99.top\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/beijian99.top\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/beijian99.top\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1104"}],"version-history":[{"count":4,"href":"https:\/\/beijian99.top\/index.php?rest_route=\/wp\/v2\/posts\/1104\/revisions"}],"predecessor-version":[{"id":1130,"href":"https:\/\/beijian99.top\/index.php?rest_route=\/wp\/v2\/posts\/1104\/revisions\/1130"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/beijian99.top\/index.php?rest_route=\/wp\/v2\/media\/1128"}],"wp:attachment":[{"href":"https:\/\/beijian99.top\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1104"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/beijian99.top\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1104"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/beijian99.top\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1104"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}