chapter9_4 非抢占式的多线程
协同程序与常规的多线程不同之处:协同程序是非抢占式的。
当一个协同程序运行时,是无法从外部停止它的。只有当协同程序显式地调用yield时,它才会停止。
当不存在抢先时,编程会变得简单很多,无须为同步的bug抓狂。
在程序中所有的同步都是显式的,只需要确保一个协同程序在它的临界区域之外调用yield即可。
对于这样非抢占式的多线程来说,只要有一个线程调用了一个阻塞操作,整个程序在该操作完成前,都会停止下来。
下面用一个有趣的方法来解决这个问题:通过HTTP下载几个远程文件。
下面的例子测试下载lua源代码,其中会用到LuaSocket模块:
local socket = require "socket" local host = "www.lua.org"
local file1 = "/ftp/lua-5.3.3.tar.gz " local HTTP = "HTTP/1.0\r\nUser-Agent: Wget/1.12 (linux-gnu)\r\nAccept: */*\r\nHost: www.lua.org\r\nConnection: Keep-Alive\r\n\r\n" local sock = assert(socket.connect(host1,))
sock:send("GET " .. file1 .. HTTP) repeat
local chunk,status,partial = sock:receive()
print("chuck:size: ",string.len(chunk or partial),status or "ok")
until status == "closed" sock:close()
在正常情况下receive函数会返回一个字符串。若发生错误,则返回nil,并且附加错误码及出错前读取到的内容(partial).
接下来下载几个文件,最笨的办法就是逐个下载,但是太慢。程序大部分时间花费在等待数据的接收上。
更明确地说,是将时间花在了receive阻塞调用上。
采用的解决办法是,当一个链接没有可用数据时,程序便可以从其他链接出读取数据。
很明显协同程序提供了一种简便的方式来构建这种并发下载。
为每个下载任务创建一个新的线程,只要一个线程无可用数据,它就把控制权转让给一个简单的调度程序。
而这个调度程序则会调用其他下载线程。
在以协同程序来重写程序,先将前面的下载代码重新写:
function receive(connection)
local s,status,partial = connection:receive(^)
return s or partial,status
end function download(host,file)
local sock = assert(socket.connect(host,))
local count = --记录接收到的字节数
sock:send("GET " .. file .. HTTP)
repeat
local chunk,status = receive(sock)
count = count + #chunk
until status == "closed"
sock:close()
print(file,count)
end
download(host,file1)
这是下载一个文件的函数封装,只需调用download就可以。单独下载一个文件需要18秒左右。
但是在并发的情况中,receive代码不能阻塞,因此在它没有可用数据时应该挂起:
function receive(connection)
connection:settimeout() --设置为非阻塞
local s,status,partial = connection:receive(^)
if status == "timeout" thencoroutine.yield(connection)
end
return s or partial,status
end
settimeout的调用,使得对此链接的操作不会阻塞。
即使在超时的情况下,连接也是会返回已经读取到的内容,即记录在partial变量中。
以下代码用table threads为调度程序保存所有正在运行中的线程。
get函数保证每个下载任务都在一个独立的线程中执行。
调度程序本身主要就是一个循环,遍历所有的线程,逐个唤醒它们的执行。
当有线程完成时,就将该线程从列表中删除。
threads = {} --保存活跃线程的表
function get(host,file)
local co = coroutine.create(function() --创建协同程序
download(host,file)
end)
table.insert(threads,co) --插入列表
end
function dispatch()
local i =
while true do
if threads[i] == nil then --没有线程了
if threads[] == nil then break end --表是空表吗
i = --重新开始循环
end
local status,res = coroutine.resume(threads[i]) --唤醒改线程继续下载文件
if not res then --线程是否已经完成了任务
table.remove(threads,i) --移除list中第i个线程
else
i = i + --检查下一个线程
end
end
end
最后,主程序需要创建所有的线程,并调用调度程序。
local file1 = "/ftp/lua-5.3.3.tar.gz "
local file2 = "/ftp/lua-5.3.2.tar.gz "
local file3 = "/ftp/lua-5.3.1.tar.gz "
local file4 = "/ftp/lua-5.3.0.tar.gz "
local file5 = "/ftp/lua-5.2.4.tar.gz "
local file6 = "/ftp/lua-5.2.3.tar.gz "
local file7 = "/ftp/lua-5.2.2.tar.gz "
local file8 = "/ftp/lua-5.2.1.tar.gz "
local file9 = "/ftp/lua-5.2.0.tar.gz "
get(host,file1)
get(host,file2)
get(host,file3)
get(host,file4)
get(host,file5)
get(host,file6)
get(host,file7)
get(host,file8)
get(host,file9) dispatch() --main loop
同时下载9个文件总共耗时36秒,比串行下载9个文件速度快很多。
但是发现CPU占用率跑到98%。
为了避免这样的情况,可以使用LuaSocket中的select函数(socket.select(recvt, sendt [, timeout]))。
在等待时陷入阻塞状态,若要在当前实现中应用这个函数,只需要修该调度即可:
function dispatch_new()
local i =
local timedout = {} --Recvt 集合
while true do
if threads[i] == nil then --没有线程了
if threads[] == nil then break end --表是空表
i = --重新开始循环
timedout = {} --遍历完所有线程,开始新一轮的遍历
end
local status,res = coroutine.resume(threads[i]) --唤醒该线程继续下载文件
if not res then --若完成了res就为nil,只有status一个返回值true。否则res为yield传入的参数connection。
table.remove(threads,i) --移除list中第i个线程
else
i = i + --检查下一个线程
timedout[#timedout +] = res
if #timedout == #threads then --所有线程都阻塞了吗?
socket.select(timedout) --如果线程有数据,就返回
end
end
end
end
... ...
dispatch_new() --main loop
receive会将超时的连接通过yield传给resume的res。如果所有的连接都超时了,调度程序就用select来等待这些链接的状态发生变化。
最后运行改良版的程序后,9个文件下载总耗时24秒,cpu占用率不到5%。
chapter9_4 非抢占式的多线程的更多相关文章
- 非抢占式RCU实现(一)
关于RCU的实现,考虑如下情形: 1.非抢占式RCU 2.限于嵌入式系统4核.每核单线程 3.RCU_FANOUT = 32 此时,RCU_TREE退化为单节点,如下,针对rcu_sched_stat ...
- 非抢占式RCU中关于grace period的处理(限于方法)
参考自:http://blog.csdn.net/junguo/article/details/8244530 Documentation/RCU/* TREE_RCU将所有的 ...
- 非抢占式RCU中的一些概念
该记录着重介绍下:2.6.34版本中非抢占式RCU的基本概念. RCU保护的是指针,因为指针的赋值可以使用原子操作完成: 在非抢占式RCU中: 对于读者,RCU仅需要抢占失效,因此获得读锁和释放读锁分 ...
- 抢占式内核与非抢占式内核中的自旋锁(spinlock)的差别
一.概括 (1)自旋锁适用于SMP系统,UP系统用spinlock是作死. (2)保护模式下禁止内核抢占的方法:1.运行终端服务例程时2.运行软中断和tasklet时3.设置本地CPU计数器preem ...
- 一种基于C51单片机的非抢占式的操作系统架构
摘 要:从Keil C51的内存空间管理方式入手,着重讨论实时操作系统在任务调度时的重入问题,分析一些解决重入的基本方式与方法:分析实时操作系统任务调度的占先性,提出非占先的任务调度是能更适合于Kei ...
- 非抢占式RCU实现(二),解释:为什么 RCU_NEXT_SIZE 宏值是4?
参考:2.6.34 一个很奇怪的问题. 没有查找到为什么 RCU_NEXT_SIZE的值为4的原因(包括Documentation),主要是在rcu_state中定义了一个四级的list,感到很有意思 ...
- keepalived的抢占与非抢占模式
目录 一:keepalived的抢占与非抢占模式 1.抢占模式 2.非抢占模式 二:接下来分4种情况说明 三:以上3种,只要级别高就会获取master,与state状态是无关的 一:keepalive ...
- Java基础知识强化之多线程笔记07:同步、异步、阻塞式、非阻塞式 的联系与区别
1. 同步: 所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回.但是一旦调用返回,就必须先得到返回值了. 换句话话说,调用者主动等待这个"调用"的结果. 对于 ...
- ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务
★PART1:中断和异常概述 1. 中断(Interrupt) 中断包括硬件中断和软中断.硬件中断是由外围设备发出的中断信号引发的,以请求处理器提供服务.当I/O接口发出中断请求的时候,会被像8259 ...
随机推荐
- js中setTimeout()的使用
setTimeout()在js类中的使用方法 setTimeout (表达式,延时时间)setTimeout(表达式,交互时间)延时时间/交互时间是以豪秒为单位的(1000ms=1s) setTi ...
- 我喜欢的快捷键 webstorm
1.打开设置 ctrl+alt+s 2.重命名 rename ctrl+r
- Malware Defender(HIPS主动防御软件) V2.8 免费版
软件名称: Malware Defender(HIPS主动防御软件) V2.8 免费版 软件语言: 简体中文 授权方式: 免费软件 运行环境: Win7 / Vista / Win2003 / Win ...
- CSU 1811 Tree Intersection
莫队算法,$dfs$序. 题目要求计算将每一条边删除之后分成的两棵树的颜色的交集中元素个数. 例如删除$u->v$,我们只需知道以$v$为$root$的子树中有多少种不同的颜色(记为$qq$), ...
- Jquery几秒自动跳转
$(document).ready(function() { function jump(count) { window.setTimeout(function(){ count--; if(coun ...
- eclipse安装插件的4种方式
Eclipse插件的安装方法大体有以下三种:[9] 第一种:直接复制法 假设Eclipse的安装目录在C:\eclipse,解压下载的eclipse 插件或者安装eclipse 插件到指定目录AA(如 ...
- Haskell Json数据处理
json的基本类型为——string, numbers, Booleans以及null,定义json类型如下 -- file: Json.hs module Json where data JValu ...
- CCF-出现次数最多的数
试题名称: 出现次数最多的数 试题编号:201312-1 时间限制: 1.0s 内存限制: 256.0MB 问题描述 给定n个正整数,找出它们中出现次数最多的数.如果这样的数有多个,请输出其中最小的一 ...
- 一个3D视频播放器的演示APK
介绍: 这个APK是把视频显示分割成左右对等的两幅画面.同时无缝显示在屏幕上, 配合类似谷歌的cartdboard "纸片壳" 或市面上的魔镜等3D眼镜来播放视频画面, 根据3D眼 ...
- java中|与||有什么区别?那么&与&&呢
||当左边为真时,就不运行右边的表达式了|当左边为真,还是会运算右边的表达式&&当左边为假时,就不会运算右边的表达式&当左边为假时,还是会运算右边的表达式