使用go来实现类似erlang otp里面的gen_server功能

时间:2022-06-01 12:57:21

erlang比go要成熟,其中一大原因就是拥有otp工程,进程的管理可以通过专门的行为

模式去处理,例如gen_server,里面包含的6个回调函数init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3

来实现进程的一系列行为,例如启动进程,异步调用,同步调用,响应,关闭进程等

先来看一段gen_server的内部实现代码:

-module(server_templat
-export([start/1]).
-export([call/2, cast/2]).
-export([init/1]).

%%通用进程模式(可以认为是c/s模型的阻塞和非阻塞)

start(Mod) ->
  spawn(server_template, init, [Mod]). 
init(Mod) ->
    register(Mod, self()),
    State = Mod:init(),
    loop(Mod, State).
   
loop(Mod, State) ->
    receive
        {call, From, Req} ->
            {Res, State2} = Mod:handle_call(Req, State),
            From ! {Mod, Res},
            loop(Mod, State2);
        {cast, Req} ->
            State2 = Mod:handle_cast(Req, State),
            loop(Mod, State2);
         stop ->   
             stop   
    end.
   
%% 接口部分  
call(Name, Req) ->
    Name ! {call, self(), Req},
    receive				%% 这里就是阻塞在等待返回,精辟!!!
        {Name, Res} ->
            Res
    end.
cast(Name, Req) ->
    Name ! {cast, Req},
    ok.

  

能看懂erlang代码的,应该明白上面是什么意思。

下面是我用go来实现的

package global

import (
	"time"
)

type PidObj struct {
	Callback GenServer
	reqCh   chan GenReq
	replyCh chan Reply
	stop bool
}

type GenReq struct {
	Method	string
	MsgData interface{}
	t int
	time int32
}

type Reply interface{}

const (
	call = iota
	cast
	timer
	shutdown
)

// Start server
func RegisterPid(pidName interface{}, callback GenServer) *PidObj {
	pidObj := &PidObj{Callback:callback}
	pidObj.reqCh = make(chan GenReq, 1024)
	pidObj.replyCh = make(chan Reply, 1024)
	callback.Start()
	go pidObj.loop()
	return pidObj
}

func (p *PidObj) loop() {
	for {
		req := <-p.reqCh
		switch req.t {
		case call:
			reply := p.Callback.HandleCall(req)
			p.replyCh <- reply
		case cast:
			p.Callback.HandleCast(req)
		case timer:
			p.Callback.HandleInfo(req)
			//time.AfterFunc(time.Duration(req.time) * time.Second, func() {
			//	p.Callback.HandleInfo(req)
			//})
		case shutdown:
			p.stop = true
			close(p.reqCh)
			close(p.replyCh)
			p.Callback.Stop()
			return
		}
	}
}

func (p *PidObj) Call(method string, msg interface{}) (reply Reply) {
	p.reqCh <- GenReq{Method: method, MsgData: msg, t: call}
	reply = <-p.replyCh
	return
}

func (p *PidObj) Cast(method string, msg interface{}) {
	p.reqCh <- GenReq{Method: method, MsgData: msg, t: cast}
}

func (p *PidObj) SendAfter(method string, seconds int32, msg interface{}) {
	time.AfterFunc(time.Duration(seconds) * time.Second, func() {
		if !p.stop {
			p.reqCh <- GenReq{Method: method, MsgData: msg, t: timer, time:seconds}
		}
	})
}

func (p *PidObj) Stop() {
	p.reqCh <- GenReq{t: shutdown}
}

  

 

定义接口:

package global

// GenServer behavior needs to implement this interface
type GenServer interface {
  Start()
  HandleCall(GenReq) Reply
  HandleCast(GenReq)
  HandleInfo(GenReq)
  Stop()
}

  

上面是用go实现了类似erlang的gen_server功能,使用的时候只需要实现Start, HandleCall,HandleCast,HandleInfo和Stop

这五个接口就可以了。

ok,that's all !