在go中使用linked channels进行数据广播
在go中使用linked channels进行数据广播
原文在这里(需翻墙),为啥想要翻译这篇文章是因为在实际中也碰到过如此的问题,而该文章的解决方式很巧妙,希望对大家有用。
在go中channels是一个很强大的东西,但是在处理某些事情上面还是有局限的。其中之一就是一对多的通信。channels在多个writer,一个reader的模型下面工作的很好,但是却不能很容易的处理多个reader等待获取一个writer发送的数据的情况。
处理这样的情况,可能的一个go api原型如下:
type Broadcaster …
func NewBroadcaster() Broadcaster
func (b Broadcaster) Write(v interface{})
func (b Broadcaster) Listen() chan interface{}
broadcast channel通过NewBroadcaster创建,通过Write函数进行数据广播。为了监听这个channel的信息,我们使用Listen,该函数返回一个新的channel去接受Write发送的数据。
这套解决方案需要一个中间process用来处理所有reader的注册。当调用Listen创建新的channel之后,该channel就被注册,通常该中间process的主循环如下:
for {
select {
case v := <-inc:
for _, c := range(listeners) {
c <- v
}
case c := <- registeryc:
listeners.push(c)
}
}
这是一个通常的做法,(译者也经常这么干)。但是该process在处理数据广播的时候会阻塞,直到所有的readers读取到值。一个可选的解决方式就是reader的channel是有buffer缓冲的,缓冲大小我们可以按需调节。或者当buffer满的时候我们将数据丢弃。
但是这篇blog并不是介绍上面这套做法的。这篇blog主要提出了另一种实现方式用来实现writer永远不阻塞,一个慢的reader并不会因为writer发送数据太快而要考虑分配太大的buffer。
虽然这么做不会有太高的性能,但是我并不在意,因为我觉得它很酷。我相信我会找到一个很好的使用地方的。
首先是核心的东西:
type broadcast struct {
c chan broadcast
v interface{}
}
这就是我说的linked channel,但是其实是Ouroboros data structure。也就是,这个struct实例在发送到channel的时候包含了自己。
从另一方面来说,如果我有一个chan broadcast类型的数据,那么我就能从中读取一个broadcast b,b.v就是writer发送的任意数据,而b.c,这个原先的chan broadcast,则能够让我重复这个过程。
另一个可能让人困惑的地方在于一个带有缓冲区的channel能够被用来当做一个1对多广播的对象。如果我定义如下的buffered channel:
var c = make(chan T, 1)
任何试图读取c的process都将阻塞直到有数据写入。
当我们想广播一个数据的时候,我们只是简单的将其写入这个channel,这个值只会被一个reader给获取,但是我们约定,只要读取到了数据,我们立刻将其再次放入该channel,如下:
func wait(c chan T) T {
v := <-c
c <-v
return v
}
结合上面两个讨论的东西,我们就能够发现如果在broadcast struct里面的channel如果能够按照上面的方式进行处理,我们就能实现一个数据广播。
代码如下:
package broadcast
type broadcast struct {
c chan broadcast;
v interface{};
}
type Broadcaster struct {
// private fields:
Listenc chan chan (chan broadcast);
Sendc chan<- interface{};
}
type Receiver struct {
// private fields:
C chan broadcast;
}
// create a new broadcaster object.
func NewBroadcaster() Broadcaster {
listenc := make(chan (chan (chan broadcast)));
sendc := make(chan interface{});
go func() {
currc := make(chan broadcast, 1);
for {
select {
case v := <-sendc:
if v == nil {
currc <- broadcast{};
return;
}
c := make(chan broadcast, 1);
b := broadcast{c: c, v: v};
currc <- b;
currc = c;
case r := <-listenc:
r <- currc
}
}
}();
return Broadcaster{
Listenc: listenc,
Sendc: sendc,
};
}
// start listening to the broadcasts.
func (b Broadcaster) Listen() Receiver {
c := make(chan chan broadcast, 0);
b.Listenc <- c;
return Receiver{<-c};
}
// broadcast a value to all listeners.
func (b Broadcaster) Write(v interface{}) { b.Sendc <- v }
// read a value that has been broadcast,
// waiting until one is available if necessary.
func (r *Receiver) Read() interface{} {
b := <-r.C;
v := b.v;
r.C <- b;
r.C = b.c;
return v;
}
下面就是译者的东西了,这套方式实现的很巧妙,首先它解决了reader register以及unregister的问题。其次,我觉得它很好的使用了流式化处理的方式,当reader读取到了一个值,reader可以将其传递给下一个reader继续使用,同时自己在开始监听下一个新的值的到来。
译者自己的一个测试用例:
func TestBroadcast(t *testing.T) {
b := NewBroadcaster()
r := b.Listen()
b.Write("hello")
if r.Read().(string) != "hello" {
t.Fatal("error string")
}
r1 := b.Listen()
b.Write(123)
if r.Read().(int) != 123 {
t.Fatal("error int")
}
if r1.Read().(int) != 123 {
t.Fatal("error int")
}
b.Write(nil)
if r.Read() != nil {
t.Fatal("error nit")
}
if r1.Read() != nil {
t.Fatal("error nit")
}
}
当然,这套方式还有点不足,主要就在于Receiver Read函数,并不能很好的与select进行整合,具体可以参考该作者另一篇bloghttp://rogpeppe.wordpress.com/2010/01/04/select-functions-for-go/。
在go中使用linked channels进行数据广播的更多相关文章
- Expo大作战(二十)--expo中的Release channels(不重要*)
简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo以来,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,讲全部来与官网 我猜去全部机翻+个人 ...
- CCS中的linked resource
一.关于CCS中的linked resource linkded resources 是eclipse中的一个功能,可以将存放在项目所在位置以外某个路径的文件或者文件夹链接至工程项目中.这个功能最大的 ...
- 在SQL Server中添加Linked Server 图解版
在开发中,经常需要一个SQL Server服务器去访问另一个服务器,微软提供了一种方式Linked Server 下面是配置流程: 1).打开Server Objects下 Linked Server ...
- Linked Server for SQL Server 2012(x64) to Oracle Database 12c(x64)
因为把两台数据库装了同一台机机器上,所以没有安装oracle Client的部分,Oracle部分使用netca创建的Net Service Name,使用tnsping以及登入方式的确认用户权限的以 ...
- 实时 Django 终于来了 —— Django Channels 入门指南
Reference: http://www.oschina.net/translate/in_deep_with_django_channels_the_future_of_real_time_app ...
- Django Channels 入门指南
http://www.oschina.NET/translate/in_deep_with_django_channels_the_future_of_real_time_apps_in_django ...
- channels 2.x的使用
转载:https://www.vimiix.com/post/2018/07/26/channels2-tutorial/ 认识 Channels 之前,需要先了解一下 asgi ,全名:Asynch ...
- Django使用Channels实现WebSocket--上篇
WebSocket - 开启通往新世界的大门 WebSocket是什么? WebSocket是一种在单个TCP连接上进行全双工通讯的协议.WebSocket允许服务端主动向客户端推送数据.在WebSo ...
- 【翻译】Django Channels 官方文档 -- Tutorial
Django Channels 官方文档 https://channels.readthedocs.io/en/latest/index.html 前言: 最近课程设计需要用到 WebSocket,而 ...
随机推荐
- python实现编写windows服务
使用python编写windows服务 最近测试服务器上经常发生磁盘空间不足,每次手动清除比较麻烦,所以写个windows服务定时清理下.中间也遇到过几个坑,一起记录下来. 1.python实现win ...
- c++银行家算法
#include <iostream> #include<string> #define False 0 #define True 1 using namespace std; ...
- Java并发编程(一)-为什么要并发
并发所带来的好处 1. 并发在某些情况(并不是所有情况)下可以带来性能上的提升 1) 提升对CPU的使用效率 提升多核CPU的利用率:一般来说一台主机上的会有多个CPU核心,我们可以创建多个线程,理论 ...
- Java中使用CountDownLatch进行多线程同步
CountDownLatch介绍 在前面的Java学习笔记中,总结了Java中进行多线程同步的几个方法: 1.synchronized关键字进行同步. 2.Lock锁接口及其实现类ReentrantL ...
- 安卓Tv开发(一)移动智能电视之焦点控制(触控事件)
前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家居,以及可穿戴设备的大量使用,但是这些设备上的开发并不是和传统手机开发一样,特别是焦点控制和用户操作体验风格上有很大的区别,本系列博 ...
- [BBS]搭建开源论坛之JForum安装使用札记
本文作者:sushengmiyan 本文地址:http://blog.csdn.net/sushengmiyan/article/details/47761303 目录 目录 BBS搭建开源论坛之JF ...
- 带有进度条的WebView
带有进度条的WebView 本篇继于WebView的使用 效果图 自定义一个带有进度条的WebView package com.kongqw.kbox.view; import android.con ...
- zookeeper分布式锁
摘要:分享牛原创,zookeeper使用,zookeeper锁在实际项目开发中还是很常用的,在这里我们介绍一下zookeeper分布式锁的使用,以及我们如何zookeeper分布式锁的原理.zooke ...
- Dynamics CRM 通过Odata创建及更新记录各类型字段的赋值方式
CRM中通过Odata方式去创建或者更新记录时,各种类型的字段的赋值方式各不相同,这里转载一篇博文很详细的列出了各类型字段赋值方式,以供后期如有遗忘再次查询使用. http://luoyong0201 ...
- Android开发学习之路--Drawable mutations
时间过得很快,明天终于可以拿到房子了,交完这次房租,也可以成为房东了,看看博客也好久没有更新了,最近一直在整机器人,也没有太多时间整理博客. 今天下午和同事一起遇到了一个问题,就是明明没有改变 ...