Lua程序设计(七十六)

使用协程实现多线程

协程能够实现一种协作式多线程。每个协程都等于一个线程。一对 yield-resume 可以将执行权在不同线程之间切换。不过,与普通的多线程不同的是,协程是非抢占的。当一个协程正在运行时,是无法从外部停止的。只有当协程显式要求即通过调用函数 yield, 它才会挂起执行。

不过,对于非抢占式多线程来说,只要有一个线程调用了阻塞操作,整个程序在该操作完成前都会阻塞。有一个有趣的方法可以解决这个问题。

首先假设一个多线程场景,譬如通过 HTTP 下载多个远程文件。为了下载多个远程文件,我们必须知道如何下载一个远程文件。要下载一个文件,必须先打开一个到对应站点的连接,然后发送下载文件的请求,接受文件(按块),最后关闭连接。

local socket = require "socket"
//定义主机和要下载的文件
host = "www.lua.org"
file = "/manual/5.3/manual.html"
//打开一个TCP连接,连接到该站口的80端口
c = assert(socket.connect(host, 80))

local request = string.format(
	"Get %s HTTP/1.0\r\nhost: %s\r\n\r\n", file, host)
c:send(request)

//接下来,以1KB为一块读取文件,并将每块写入到标准输出中:

repeat
	local s, status, partial = c:receive(2^10)
	io.write(s or partial)
until status == "closed"
//下载完成关闭
c:close()

最简单的下载多个远程文件的方法就是逐个地串行下载,但这种方式太慢了,程序的大部分时间都用在了等待数据到达上。

我们可以为每一个下载任务创建线程,当一个线程无可用数据时,它就可以将控制权传递给一个简单的调度器,这个调度器再去调用其他的线程。

//下载Web页面
function download (host, file)
	local c = assert(sockect.connect(host, 80))
	local count = 0
	local request = string.format(
		"Get %s HTTP/1.0\r\nhost: %s\r\n\r\n", file, host)
	c:send(requres)
	while true do
		local s, status = receive(c)
		count = count + #s
		if status == "closed" then break end
	end
	c:close()
	print(file, count)
end

//串行下载
function receive (connection)
	local s, status, partial = connection:receive(2^10)
	return s or partial, status
end

//并行实现中,在没有足够的可用数据时,该函数会挂起
function receive (connection)
	connection:settimeout(0)
	local s, status, partial = connection:receive(2^10)
	if status == "timeout" then
		coroutine.yield(connection)
	end
	return s or partial, status
end

//调度器
tasks = {}

function get (host, file)
	local co = coroutine.wrap(function()
		download(host, file)
	end)
	table.insert(tasks,co)
end

function dispatch ()
	local i = 1
	while true do
		if tasks[i] == nil then
			if tasks[1] == nil then
				break
			end
			i = 1
		end
		local res = tasks[i]()
		if not res then
			talbe.remove(tasks, i)
		else
			i = i + 1
		end
	end
end

tasks 为调度器保存所有正在运行线程的列表。函数 get 保证每一个下载任务运行在一个独立的线程中。调度器本身就是一个循环,它遍历所有的线程,逐个唤醒它们。调度器同时还需要在线程完成任务后,将线程从列表中删除。所有的线程都完成后,调度器就停止循环。

还有一个很大的优化空间为,如果所有的线程都没有数据可读,调度程序就会陷入忙等待,不断地从一个线程切换到另一个线程来检查是否有数据可读。

为了避免这样的情况可以使用 LuaSocket 中的 select 函数,该函数允许程序阻塞知道一套接字的状态发生改变。

function newDispatch()
	local i = 1
	local timedout = {}
	while true do
		if tasks[i] == nil then
			if tasks[1] == nil then
				break
			end
			i = 1
			timedout = {}
		end
		local res = tasks[i]()
		if not res then
			table.remove(tasks, i)
		else
			i = i + 1
			timedout[#timedout + 1] = res
			if #timedout == #tasks then
				socket.select(timedout)
			end
		end
	end
end

在循环中,调度器将把所有超时的连接收集到表 timedout 中。

  • 0
    点赞
  • 0
    收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sxylyr

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值