golang select default continue_15. 理解 Go 语言中的 select 用法

本文介绍了Go语言中Select机制的基本用法,包括如何避免死锁、实现超时机制以及Select的特点等。通过多个实例展示了Select在信道操作中的灵活性。

ca5e945fafffe421e5d306b8eaa1b67e.png

大家好,我是明哥。

由于某些不可抗力,之前该专栏的所有内容都被删除了。因此之后的一段时间内,我会重新发布这些文章,先给已经看过的关注者提个醒。


本文原文:http://golang.iswbm.com

Github:http://github.com/iswbm/GolangCodingTime

前面写过两节关于 switch-case 的文章,分别是:

流程控制:switch-case

Go 语言中的类型断言

今天要学习一个跟 switch-case 很像,但还有点个人特色select-case,这一节本应该放在 学习 Go 协程:详解信道/通道 里一起讲的,但是当时漏了,直到有读者给我提出,才注意到,今天就用这篇文章补充一下。

跟 switch-case 相比,select-case 用法比较单一,它仅能用于 信道/通道 的相关操作。

select {
    case 表达式1:
        <code>
    case 表达式2:
        <code>
  default:
      <code>
}

接下来,我们来看几个例子帮助理解这个 select 的模型。

1. 最简单的例子

先创建两个信道,并在 select 前往 c2 发送数据

package main

import (
    "fmt"
)

func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)

    c2 <- "hello"

    select {
    case msg1 := <-c1:
      fmt.Println("c1 received: ", msg1)
    case msg2 := <-c2:
      fmt.Println("c2 received: ", msg2)
    default:
      fmt.Println("No data received.")
    }
}

在运行 select 时,会遍历所有(如果有机会的话)的 case 表达式,只要有一个信道有接收到数据,那么 select 就结束,所以输出如下

c2 received:  hello

2. 避免造成死锁

select 在执行过程中,必须命中其中的某一分支。

如果在遍历完所有的 case 后,若没有命中(命中:也许这样描述不太准确,我本意是想说可以执行信道的操作语句)任何一个 case 表达式,就会进入 default 里的代码分支。

但如果你没有写 default 分支,select 就会阻塞,直到有某个 case 可以命中,而如果一直没有命中,select 就会抛出 deadlock 的错误,就像下面这样子。

package main

import (
    "fmt"
)

func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)

    // c2 <- "hello"

    select {
    case msg1 := <-c1:
        fmt.Println("c1 received: ", msg1)
    case msg2 := <-c2:
        fmt.Println("c2 received: ", msg2)
        // default:
        //     fmt.Println("No data received.")
    }
}

运行后输出如下

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [select]:
main.main()
        /Users/MING/GolandProjects/golang-test/main.go:13 +0x10f
exit status 2

解决这个问题的方法有两种

一个是,养成好习惯,在 select 的时候,也写好 default 分支代码,尽管你 default 下没有写任何代码。

package main

import (
    "fmt"
)

func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)

  // c2 <- "hello"

    select {
    case msg1 := <-c1:
        fmt.Println("c1 received: ", msg1)
    case msg2 := <-c2:
        fmt.Println("c2 received: ", msg2)
    default:

    }
}

另一个是,让其中某一个信道可以接收到数据

package main

import (
    "fmt"
    "time"
)

func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)

  // 开启一个协程,可以发送数据到信道
    go func() {
        time.Sleep(time.Second * 1)
        c2 <- "hello"
    }()

    select {
    case msg1 := <-c1:
        fmt.Println("c1 received: ", msg1)
    case msg2 := <-c2:
        fmt.Println("c2 received: ", msg2)
    }
}

3. select 随机性

之前学过 switch 的时候,知道了 switch 里的 case 是顺序执行的,但在 select 里却不是。

通过下面这个例子的执行结果就可以看出

5eb8b44701679dfa0bc85dae9447d961.png

4. select 的超时

当 case 里的信道始终没有接收到数据时,而且也没有 default 语句时,select 整体就会阻塞,但是有时我们并不希望 select 一直阻塞下去,这时候就可以手动设置一个超时时间。

package main

import (
    "fmt"
    "time"
)

func makeTimeout(ch chan bool, t int) {
    time.Sleep(time.Second * time.Duration(t))
    ch <- true
}

func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)
    timeout := make(chan bool, 1)

    go makeTimeout(timeout, 2)

    select {
    case msg1 := <-c1:
        fmt.Println("c1 received: ", msg1)
    case msg2 := <-c2:
        fmt.Println("c2 received: ", msg2)
    case <-timeout:
        fmt.Println("Timeout, exit.")
    }
}

输出如下

Timeout, exit.

5. 读取/写入都可以

上面例子里的 case,好像都只从信道中读取数据,但实际上,select 里的 case 表达式只要求你是对信道的操作即可,不管你是往信道写入数据,还是从信道读出数据。

package main

import (
    "fmt"
)

func main() {
    c1 := make(chan int, 2)

    c1 <- 2
    select {
    case c1 <- 4:
        fmt.Println("c1 received: ", <-c1)
        fmt.Println("c1 received: ", <-c1)
    default:
        fmt.Println("channel blocking")
    }
}

输出如下

c1 received:  2
c1 received:  4

6. 总结一下

select 与 switch 原理很相似,但它的使用场景更特殊,学习了本篇文章,你需要知道如下几点区别:

  1. select 只能用于 channel 的操作(写入/读出),而 switch 则更通用一些;
  1. select 的 case 是随机的,而 switch 里的 case 是顺序执行;
  1. select 要注意避免出现死锁,同时也可以自行实现超时机制;
  1. select 里没有类似 switch 里的 fallthrough 的用法;
  1. select 不能像 switch 一样接函数或其他表达式。

好了,今天的文章就到这里了。

若今天的分享对你有帮助,不如点个赞,支持一下?

首页/编程语言 golang多协程读取变量一致性解决方案 golang websocket //websocket基础定义 package tools import ( "github.com/google/uuid" "github.com/gorilla/websocket" "sync" "time" "sync/atomic" ) //websocket连接 type WebSocketConn struct{ ClientId *string //唯一标识一条连接 Conn *websocket.Conn //主播对应的conn连接 CreateAt int64 //连接创建时间 LatHeartbeatAt int64 //心跳最后回复时间 sync.Mutex } type WebSocketConnMap struct{ ConnCount int32 //有效连接数 ConnMap map[*string]*WebSocketConn //*string对应uuid sync.Mutex } var ( WebSocketConnOnce *WebSocketConn WebSocketConnMapOnce *WebSocketConnMap ) func NewWebSocketConn() *WebSocketConn{ var once sync.Once once.Do(func(){ WebSocketConnOnce = new(WebSocketConn) }) return WebSocketConnOnce } func NewWebSocketConnMap() *WebSocketConnMap{ var once sync.Once once.Do(func(){ WebSocketConnMapOnce = new(WebSocketConnMap) WebSocketConnMapOnce.ConnMap = make(map[*string]*WebSocketConn,0) }) return WebSocketConnMapOnce } //初始化 func init(){ NewWebSocketConnMap() NewWebSocketConn() } //调用: //缓存池 var ws_buff_pool = sync.Pool{ New: func() interface{} { return new(bytes.Buffer) }, } //websocket缓冲区、跨域设置 var upgrader = websocket.Upgrader{ ReadBufferSize : ws_buff, WriteBufferSize : ws_buff, CheckOrigin : chk_origin, //设置跨域 WriteBufferPool : &ws_buff_pool, //设置缓存池 } var ws_conn_obj = tools.WebSocketConnOnce //创建一个新的websocket连接 func (ws *WsChat) WsChatCreateConn(user_info *models.LuUserInfo,w http.ResponseWriter, r *http.Request) (conn *websocket.Conn,err error){ conn, err = upgrader.Upgrade(w, r, nil) if err != nil{ return } var ( exit_chan = make(chan types.Signal) ) //连接建立成功,则下发唯一id,以后使用该id通信 client_id := ws_conn_obj.CreateWebSocketConn(conn) //建立账号和连接的绑定关系,并作为链表的一个节点 item := chat_websocket_conn{ user_conn : tools.WebSocketConnMapOnce.ConnMap[client_id], client_id : *client_id, user_type : (*user_info).Flag, user_id : (*user_info).Id, user_nickname : (*user_info).NickName, } linked_node := tools.SingleNode{Data:item} linked_list.LastAppend(&linked_node) bytes := pack_msg((*user_info).Id,types.INT64_ZERO,types.INT64_ZERO,(*user_info).NickName,types.STRING_ZERO,types.STRING_ZERO,types.STRING_ZERO,types.INT64_ZERO,types.WS_SYNC_SUCC,client_id) ws_conn_obj.WriteMsg(client_id, &bytes) //读取一个连接的数据 go ws.WsChatMsgRead(client_id,item.user_conn.Conn) //写入一个连接的数据 go ws.WsChatMsgWrite(client_id,item.user_conn.Conn) <-exit_chan return } WsChatMsgRead: //读取客户端发送过来的消息 func (ws *WsChat) WsChatMsgRead(client_id *string,conn *websocket.Conn){ defer func(){ //从链表中删除每个主播的基础数据 linked_list.DelLinkedNode(delete_linked_node_find(client_id)) //从websocket连接map中删除该连接,并将连接总数减一 tools.WebSocketConnMapOnce.CloseWebSocketMap(client_id) linked_list.Display() }() //设置读取大小,超过该值,连接关闭并返回1009错误码 conn.SetReadLimit(ws_read_size_limit) //设置读取超时 SetReadDeadline(ws_read_timeout_limit) for { msg_type, msg, err := conn.ReadMessage() if err != nil{ if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) { logx.Error(fmt.Sprintf("tags:%+v,连接正常关闭",ws.LogTag)) }else{ logx.Error(fmt.Sprintf("tags:%+v,连接非正常关闭,err:%+v",ws.LogTag,err)) } break } if msg_type != websocket.TextMessage{ continue } fmt.Printf("收到.消息类型:%+v,消息内容:%+v,err:%+v\n",msg_type, string(msg), err) } return } WsChatMsgWrite: //写入数据到客户端 func (ws *WsChat) WsChatMsgWrite(client_id *string,conn *websocket.Conn){ conn.SetWriteDeadline(ws_write_timeout_limit) content : = []bytes("收到消息,谢谢") //限制读取500K if err := tools.WebSocketConnOnce.WriteMsg(content); err != nil { fmt.Printf("写入消息错误\n") } return } //WsChatMsgWrite、WsChatMsgRead都需要读取websocket.Conn里的Conn连接,但多协程处理的过程中,A协程改变了这个conn,B协程不一定能感知到,也就是内存模型的问题,atomic.Store是否可以解决这个问题呢
最新发布
10-19
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值