通过 skynet 学习到的 assert 的使用

in 编程
关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9

assert 用于断言某条件必须成立,否者就会触发断言失败,比如在 C 中进程会退出并打印出行号,而 Lua 中打印出 traceback 。
有时候会听人说要学会使用 assert 。当时并不理解,因为 assert 就是断言某条件必须成立,很简单啊,怎么还特别强调要学会呢。直到后来看见关于 skynet 的多线程读写的 issue794 后,才体会到 assert 的用法,就是使用 assert 来限定代码的边界,掌控代码的执行,如果 assert 断言失败,可以很明确代码问题所在。

skynet 的 issue794 产生原因如下。在 issue777 中,有用户无法编译通过,于是云大在之后做了些修改,将 uint64_t sending 修改为 uint32_t sending 来确保在某些平台能编译通过。但是解决 issue777 后,没有想到触发了 assert 断言失败,产生了 issue794 。这里就想简单介绍一下,在解决 issue777 时增加的 assert 代码。

先说修复 issue777 编译不通过前,多线程写 socket 的实现。云大在这次提交中上传了 skynet 支持多线程写入代码。一开始定义就是 uint64_sending 被分成两部分,高 32 位是分配的 id,低 32 位表示 socket 线程中此 id 正在发送的包数量,若低 32 位为 0,则表示此时 socket 线程中还未有需要发送的数据。所以 service 需要发送数据时,在 worker 线程调用 socket_server_send 函数,此时若能直接 write 则直接调用 write 写入数据,若不能直接 write 则会增加 sending 计数,通过 socket 线程发送,发送完毕后减少 sending 计数。

在多线程写 socket 实现提交后,产生了 issue777 存在某些平台上编译不通过,于是云大改成了 uint32_t sending 。这里有一个技巧是 skynet 分配的 socket id 是递增的,具体实现见 reserve_id 函数,而且 id 被 HASH_ID
的 hash 处理也只是求余操作,那么如果 slot 1 被占用,就不会存在第二个被使用的 HASH_ID(id) 为 1 的 socket id 。所以通过 ID_TAG16 取出 slot 进而判断 socket_server.slot 是否被使用了,于是 sending 的高 16 位用于标识 id 对应的 slot 而低 16 位用于计数。这里就存在了一个前提,计数的最大值是 65535 。所以云大在这次提交中的第 571 行加了 assert 确保计数一超过 65535 就会断言失败。真的有情景触发了断言失败,然后就产生了前文提到的的 issue794 问题。由于 bug 触发了 assert 断言失败,于是可以目的很明确的修复这个问题。话说回来,多线程写 socket 还真是挺复杂的。

其实 Lua 的代码中也在很多地方用了 assert 。不过 Lua 代码比较复杂,这里再举个例子,比如 skynet 中的 skynet.queue 模块,其中有用 assert 。位于 skynet/lualib/skynet/queue.lua 文件。部分代码如下。

function skynet.queue()
	local current_thread
	local ref = 0
	local thread_queue = {}

	return function(f, ...)
		local thread = coroutine.running()
		if current_thread and current_thread ~= thread then
			table.insert(thread_queue, thread)
			skynet.wait()
			assert(ref == 0)	-- current_thread == thread
		end
		current_thread = thread

		ref = ref + 1
		return xpcall_ret(xpcall(f, traceback, ...))
	end
end

其中 assert(ref == 0) 确保之前的 current_thread 已经执行完毕。注意,当在同一个 coroutine 中嵌套调用时 ref 是可能大于 1 的。如。

local lock = skynet.queue()
lock(function()
	print("one")
	lock(function()
		print("two")
	end)
end)

总之 assert 真的是个好东西。

关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9
扫一扫关注公众号添加购物返利助手,领红包
Comments are closed.

推荐使用阿里云服务器

超多优惠券

服务器最低一折,一年不到100!

朕已阅去看看