处理不同类型的消息 - 一个或多个频道?

时间:2015-03-25 20:59:56

标签: go channel type-switch

考虑这个简单的代码:

type Message struct { /* ... */ }
type MyProcess struct {
    in chan Message
}

func (foo *MyProcess) Start() {
    for msg := range foo.in {
        // handle `msg`
    }
    // someone closed `in` - bye
}

我想更改MyProcess以支持2种不同类型的消息。 我有两个想法:

a)键入开关

type Message struct { /* ... */ }
type OtherMessage struct { /* ... */ }
type MyProcess struct {
    in chan interface{} // Changed signature to something more generic
}

func (foo *MyProcess) Start() {
    for msg := range foo.in {
        switch msg := msg.(type) {
        case Message:
            // handle `msg`
        case OtherMessage:
            // handle `msg`
        default:
            // programming error, type system didn't save us this time.
            // panic?
        }
    }
    // someone closed `in` - bye
}

b)两个频道

type Message struct { /* ... */ }
type OtherMessage struct { /* ... */ }
type MyProcess struct {
    in      chan Message
    otherIn chan OtherMessage
}

func (foo *MyProcess) Start() {
    for {
        select {
        case msg, ok := <-foo.in:
            if !ok {
                // Someone closed `in`
                break
            }
            // handle `msg`
        case msg, ok := <-foo.otherIn:
            if !ok {
                // Someone closed `otherIn`
                break
            }
            // handle `msg`
        }
    }
    // someone closed `in` or `otherIn` - bye
}
  1. 两种实现之间的功能区别是什么?有一点是排序差异 - 只有第一个保证消息(MessageOtherMessage)将按正确顺序处理。

  2. 哪一个更惯用?方法“a”较短但不强制实施消息类型正确性(可以在通道中放置任何内容)。方法'b'解决了这个问题,但有更多样板和更多人为错误的空间:需要检查两个通道的封闭性(容易忘记),有人需要实际关闭它们(更容易忘记)。

  3. 长话短说我宁愿使用'a',但它不利用类型系统,因此感觉很难看。也许还有更好的选择?

2 个答案:

答案 0 :(得分:3)

我也会选择&#39; a&#39;:只有一个频道。如果您创建基本消息类型(interface)并且两种可能的消息类型都实现它(或者如果它们也是接口,它们可以嵌入它),则可以强制类型正确。

单通道解决方案的另一个优点是它是可扩展的。如果现在要处理第三种类型的消息,则可以非常轻松地添加消息并对其进行处理。在另一个的情况下:你需要一个第三个通道,如果消息类型的数量很快增加,将很难变得无法管理并使你的代码变得丑陋。同样在多通道的情况下,select随机选择一个就绪通道。如果消息在某些频道中频繁进入,则其他频道可能会挨饿,即使频道中只有一条消息,也不会再有消息。

答案 1 :(得分:2)

首先回答你的问题:

1)您已经获得了主要的功能差异,排序差异取决于频道的写入方式。在如何实现结构类型与接口类型的通道的实现方面也存在一些差异。大多数情况下,这些都是实现细节,并没有改变使用代码的大部分结果的性质,但是在您发送数百万条消息的情况下,这个实现细节可能会花费您。

2)我想说你所提供的例子都不是简单地通过阅读你的伪代码而不是其他的惯用语,因为你是从一个或两个频道读取更多地与语义和要求有关您的程序(订购,数据来源,渠道深度要求等)比其他任何东西都要多。例如,如果其中一个消息类型是&#34;停止&#34;告诉您的处理器停止阅读,或做一些可能改变未来处理消息状态的消息?也许这会在自己的频道上进行,以确保它不会因为对其他频道的未决写入而延迟。

然后你问可能有更好的选择吗?

继续使用单个通道并且不进行类型检查的一种方法是将封闭类型作为通道类型发送:

type Message struct { /* ... */}
type OtherMessage struct { /* ... */}
type Wrap struct {
    *Message
    *OtherMessage
}

type MyProcess struct {
    in chan Wrap
}

func (foo *MyProcess) Start() {
    for msg := range foo.in {
        if msg.Message != nil {
            // do processing of message here
        }
        if msg.OtherMessage != nil {
            // process OtherMessage here
        }
    }
    // someone closed `in` - bye
}

struct Wrap的一个有趣的副作用是你可以在同一个频道消息中发送MessageOtherMessage。由你决定这是否意味着什么或将要发生的事情。

应该注意的是,如果Wrap增长超过少数几种消息类型,发送一个包装实例的成本实际上可能会高于某个中断点(很容易进行基准测试),而不仅仅是发送一个接口类型并做一个类型切换。

根据类型之间的相似性,您可能想要查看的另一件事是定义一个非空接口,其中Message和OtherMessage都具有该方法接收器集;也许它将包含将解决必须进行类型切换的功能。

也许您正在阅读邮件以将其发送到排队库,而您真正需要的只是:

interface{
    MessageID() string
    SerializeJSON() []byte
}

(我只是为了说明目的而提出这个)