天天看点

【转载】erlang 进程的 hibernate

简单的理解:  

hibernate 可以令处于 idle 状态的 erlang 进程马上进行 gc,因为进程处于 receive 状态下是不会 gc 的。

在性能调优时发现,gc 对于消息发送速度的影响还是非常大的。

关于 erlang gc 问题也可以参看这个 

recently, as part of rabbitmq server development, we ran into an interesting issue regarding erlang’s per-process garbage collection. if a process is idle — not doing any work at all, simply waiting for an external event — then its garbage-collector will not run until it starts working again. the solution is to hibernate idle processes, which causes a very aggressive garbage-collection run and puts the process into a suspended state, from which it will wake when a message next arrives.

所以查看 rabbitmq 源代码可以看到里面的 gen_server/fsm 等进程统统都会使用 hibernate 。 

      下面解释下如何让进程 hibernate ,这里需要注意的是一个进程 hiberate 后,会清空 stack 栈,也就是说之前的调用关系全部没有了,也就是说hibernate 所在的函数永远不会返回,待有新消息时,进程从 hibernate(m,f,a) 里指定的 m:f 处开始运行。 

gen_server 和 gen_fsm 都提供了 hibernate 的方式: 

第一种:  在 gen_server 或 gen_fsm 回调接口返回时,指定 hibernate 。 

<a href="http://my.oschina.net/moooofly/blog/282668#">?</a>

1

<code>{next_state, nstatename, nstatedata, time1}</code>

实际这个 time1 可以指定为 hibernate,则重新回到 main loop 时会直接 hibernate 。 

这种方法可能使你的进程频繁 hibernate 又被唤醒,效率不是很好。 

正确的方式应该是,当你预期你的进程一段时间内不会收到消息才 hibernate 。 

所以推荐下面的方法,直接看下示例代码: 

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

<code>%% ====================================================================</code>

<code>%% interface function</code>

<code>join</code><code>(userid)-&gt;</code>

<code>    </code><code>io:</code><code>format</code><code>(</code><code>"join userid=~p~n"</code><code>,[userid]),</code>

<code>    </code><code>gen_fsm:sync_send_event(?module, {</code><code>join</code><code>,userid}).</code>

<code>start()-&gt;</code>

<code>    </code><code>gen_fsm:send_event(?module,start).</code>

<code>%% callback function</code>

<code>wait_player({</code><code>join</code><code>,userid},_from, state) -&gt;</code>

<code>    </code><code>print_inner_data(state),</code>

<code>    </code><code>put(p,get(p)+1),</code>

<code>    </code><code>ets:insert(state</code><code>#state.players,{p,userid}),</code>

<code>    </code><code>count=state</code><code>#state.count+1,</code>

<code>    </code><code>if</code>

<code>        </code><code>count =:= 3 -&gt;</code>

<code>            </code><code>io:</code><code>format</code><code>(</code><code>"jump to next phrase -&gt; start~n"</code><code>),</code>

<code>            </code><code>{reply,next_phrase,wait_start,state,5000};</code>

<code>        </code><code>true</code><code>-&gt;</code>

<code>            </code><code>newstate=state</code><code>#state{count=count},</code>

<code>       </code><code>{reply,stand_still,wait_player,newstate,5000}</code>

<code>    </code><code>end.</code>

<code>wait_player(timeout,state)-&gt;</code>

<code>    </code><code>io:</code><code>format</code><code>(</code><code>"timeout happened when wait_player,let's hibernate~n"</code><code>),</code>

<code>    </code><code>proc_lib:hibernate(gen_fsm, enter_loop, [?module, [], wait_player,state]).</code>

<code>wait_start(start,state)-&gt;</code>

<code>    </code><code>io:</code><code>format</code><code>(</code><code>"recv start event ~n"</code><code>),</code>

<code>    </code><code>{next_state,wait_start,state,5000};</code>

<code>wait_start(timeout,state)-&gt;</code>

<code>    </code><code>io:</code><code>format</code><code>(</code><code>"timeout happened when wait_start,let's hibernate~n"</code><code>),</code>

<code>    </code><code>proc_lib:hibernate(gen_fsm, enter_loop, [?module, [], wait_start,state]).</code>

<code>start_link()-&gt;</code>

<code>    </code><code>gen_fsm:start_link({</code><code>local</code><code>,?module}, ?module, [] ,[]).</code>

<code>init([])-&gt;</code>

<code>    </code><code>put(p,0),</code>

<code>    </code><code>{ok,</code>

<code>        </code><code>wait_player,</code>

<code>        </code><code>#state</code>

<code>        </code><code>{</code>

<code>            </code><code>count=0,</code>

<code>            </code><code>players=ets:new(players,[bag])</code>

<code>        </code><code>},</code>

<code>        </code><code>5000</code>

<code>    </code><code>}.</code>

<code>print_inner_data(state)-&gt;</code>

<code>io:</code><code>format</code><code>(</code><code>"inner data: dict=~p,ets=~p~n"</code><code>,[get(p),ets:tab2list(state</code><code>#state.players)]).</code>

运行:  

<code>2&gt; {ok,pid}=fsm:start_link().</code>

<code>{ok,&lt;0.39.0&gt;}</code>

<code>3&gt; process_info(pid,total_heap_size).</code>

<code>{total_heap_size,233}</code>

<code>4&gt; fsm:</code><code>join</code><code>(123).</code>

<code>join</code> <code>userid=123</code>

<code>inner data: dict=0,ets=[]</code>

<code>stand_still</code>

<code>timeout happened when wait_player,</code><code>let</code><code>'s hibernate</code>

<code>5&gt; process_info(pid,total_heap_size).</code>

<code>{total_heap_size,33}</code>

<code>6&gt; fsm:</code><code>join</code><code>(456).</code>

<code>join</code> <code>userid=456</code>

<code>inner data: dict=1,ets=[{p,123}]</code>

      总体上,是让进程在 main_loop 里先 timeout ,也就是证明一段时间内都没有消息到达,然后 gen_fsm 进程是发送 {'gen_event',timeout} 消息处理 timeout 事件的,所以要在每个 mod:statename 处都加个处理 timeout 的分支,来进行 timeout 处理,也就是 hibernate 掉自己,重新进入的是 enter_loop 函数,这个东西很有用呐,大家可以查看源码,我就不多说了。 

另外,示例中可以看到,hibernate 会清空 stack,但是不会影响你的进程字典和 ets 等其它东西,可以放心用了。