最近在使用skynet的过程中,遇到需要为玩家的每次请求产生一个随机序列的场景。简化如下:
main.lua中每隔1S便发出一次随机数请求:
local skynet = require "skynet"
skynet.start(function()
skynet.error("Server start")
rand = skynet.newservice("testrand")
skynet.sleep(100)
local i = 0
while i < 10 do
i = i + 1
skynet.send(rand, "lua", "rand")
skynet.sleep(100)
end
end)
testrand.lua:
local skynet = require "skynet"
skynet.start(function()
skynet.dispatch("lua", function(session, address, cmd, ...)
print(math.random())
end)
end)
开发时使用的是skynet-mingw版本,测试结果如下:
0.001251220703125 0.001251220703125 0.001251220703125 0.001251220703125 0.56356811523438 0.19329833984375 0.56356811523438 0.56356811523438 0.19329833984375 0.001251220703125
可以看到,出现了很多重复的随机数,失去了随机的效果。经测试发现,如果在main.lua中不再sleep,而是连续发出随机数请求,那么生成的随机数便不会重复了。由前面对skynet的分析大家已经了解到,skynet服务在处理消息时,不同的消息可能是由不同的工作线程处理的。然后间隔请求和连续请求唯一的区别是目标服务testrand在处理消息时,间隔请求被不同线程处理的几率比较大,而连续请求被不同线程处理的几率比较小。记得之前的分析么,工作线程拿到目标的message-queue后,是会连续处理一部分消息的,处理完之后如果没事儿会休息一会儿。
既然如此,那么就在配置中将工作线程数目thread配置为1,测试结果如下:
0.00125122070312 0.56356811523438 0.19329833984375 0.8087158203125 0.58499145507812 0.4798583984375 0.35028076171875 0.89593505859375 0.82281494140625 0.74658203125
可以看到没有重复了,验证了我们的想法。而实际中我们又发现,多个工作线程时在linux上是不会出现重复的,测试结果如下:
0.84018771676347
0.39438292663544
0.78309922339395
0.79844003310427
0.91164735751227
0.19755136920139
0.33522275555879
0.76822959445417
0.27777471067384
0.55396995553747
这个又是什么原因呢?自然得从平台的差异上去查了。我们调用的是math.random()函数,它最终调用的是rand()函数,查阅文档后我们了解到,它在linux和windows平台上是有很大区别的。在linux平台上,rand()函数不是线程安全的,它隐藏了一个全局状态,每次调用都会修改这个全局状态。所以这里你会发现间隔调用时虽然可能由不同的线程执行,但是产生的随机数却是不同的。windows平台则不然,相关的状态是储存在线程的数据结构体中的,因为我们这里没有设置随机数种子,不同线程都是以默认随机数种子开始,于是出现了不同线程的随机数序列相同的情况。