初学golang,看到golang协程的通信方式是用管道方式,而且会存在空管道读取会阻塞,满管道写入也会阻塞的情况;不由自主会联想erlang的进程间通信,并有一个疑问,为什么golang不提供更高级的像erlang的进程间以邮箱传递消息的实现方式来给协程间通信提供更高一层的封装呢?
产生上面疑问主要是第一管道通信方式存在阻塞情况,对于业务高并发应用来说,阻塞往往是要避免的地方,从而业务实现时不可避免地都要设计一个避免长期阻塞的算法;第二协程间以管道方式通信,并没有解决协程间管道寻址问题,即我要跟另外一个协程通信,那么必须找到另外一个协程的管道,并且协程间通信如果要做到通用,也要定义一个统一的管道通信结构体;这部分逻辑其实基本是通用的。
而要实现一个通用的协程间通信模块,目前个人想到的方案是:
最简单的方案一:
定义一个全局的集合,每个协程生成一个唯一的ID和唯一的管道,根据协程ID可以查找到对应的管道,协程可以注册一个唯一的别名,根据别名也可以查找到协程唯一ID;
此方案优点是简单,对于跨进程节点的协程通信,可以存储对应连接的SOCKET,把管道通信和SOCKET通信封装成统一的参数接口,即可实现通用的本节点协程间和跨节点协程间的通信;缺点是:每次查找通信管道时使用了全局锁,如果集中大量协程间通信需求时,会导致严重的互相锁等待。
以上方案的改进版二:
性能优化点是减少全局锁的使用;为此可以在协程内定义一个缓存集合,优先查找本地缓存的管道,并通过用selectl+default读取管道检查管道是否被关闭,已关闭的管道才从全局集合里查找目标协程通信的管道
另外关于协程间的同步调用,可以考虑向目标协程的管道发消息时带上一个唯一的返回管道,目标协程通过此返回管道返回同步调用的结果,发起调用的协程通知完目标协程后,用以下代码等待调用返回并加超时限制:
select{
case backVal := <-callchan:
// 同步调用的返回
...
case time.After(5*time.Second) // 同步调用5秒超时
// 返回错误异常, 表示同步调用超时
...
}
模拟erlang的邮箱逻辑的方案:
为了彻底取消全局锁,可以考虑用开多个邮箱协程,其中选举一个主控的协程用于更新最新的协程通信路由,并把最新的路由同步到所有的邮箱协程;
在取协程路由信息时,可以随机从一个邮箱协程里获取,保证高负载下的压力均衡;协程路由有变化时,固定通过主控协程来处理,保证数据的唯一性;