协程的C实现

今天正好跟ownwaterloo聊到协程,于是查了查资料,顺便写个博客记录一下吧。

我主要参考的是这篇资料http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html,是Simon Tatham提出的一个协程的C实现,非常有意思。

协程的思想主要是为了解决多个任务如何分享CPU这个问题的。线程在很多协作问题上处理的不好,而且需要锁机制,导致运行缓慢,不易理解,容易出错。协程的思想是,一系列互相依赖的协程间依次使用CPU,每次只有一个协程工作,而其他协程处于休眠状态。与线程不同,协程是自己主动让出CPU,并交付他期望的下一个协程运行,而不是在任何时候都有可能被系统调度打断。因此协程的使用更加清晰易懂,并且多数情况下不需要锁机制(或者说他本身就是一个全局锁)。

Simon的举例是一个生产者消费者例子。传统的程序可能是生产者一个循环不断产生字符,之后退出。而消费者一个循环不断读取字符,并处理之。使用时或许会用一个管道,将生产者的输出重定向到消费者的输入。从而使两者协作。

Simon提出,如何使这样的简单任务无需通过管道这类操作系统相关的重型机制,就能完成。他提出,生产者或者消费者之间,有一方需要改变自己的工作模式。不再是在循环中不断处理任务,而是一次只处理一个字符然后立刻返回。而另一方依旧保持原状,只需在原本输出到或读取自标准IO的地方修改为调用对方的那个函数就可以了。

但这种写法难以维护。他提出了协程的概念,并期待写出类似这样的代码:

int function(void) {
    int i;
    for (i = 0; i < 10; i++)
        return i;   /* won't work, but wouldn't it be nice */
}

在这里return语句并不是终止函数,而是将函数转入睡眠的状态,并将CPU交付给另一个函数执行。例如生产者写入一个字符后,调用类似的return语句,交付消费者处理这个字符。稍后当消费者处理完并调用return语句后,能重新激活生产者协程,并继续这个for循环。

如何在C语言中实现这样的功能而不需使用汇编代码去hack堆栈和寄存器呢?他给出的最初的实现是使用goto语句。

int function(void) {
    static int i, state = 0;
    switch (state) {
        case 0: goto LABEL0;
        case 1: goto LABEL1;
    }
    LABEL0: /* start of function */
    for (i = 0; i < 10; i++) {
        state = 1; /* so we will come back to LABEL1 */
        return i;
        LABEL1:; /* resume control straight after the return */
    }
}

巧妙的使用静态变量存储函数状态,并使用goto语句来继续for循环。但这种写法不够优美,于是又引入了Duff’s device。

switch (count % 8) {
        case 0:        do {  *to = *from++;
        case 7:              *to = *from++;
        case 6:              *to = *from++;
        case 5:              *to = *from++;
        case 4:              *to = *from++;
        case 3:              *to = *from++;
        case 2:              *to = *from++;
        case 1:              *to = *from++;
                       } while ((count -= 8) > 0);
    }

使用这种trick来将switch-case语句作为循环中的跳转。这就避免了goto-label语句需要同时维护goto和label两处代码的麻烦。于是前面的代码可以变成这个样子。

int function(void) {
    static int i, state = 0;
    switch (state) {
        case 0: /* start of function */
        for (i = 0; i < 10; i++) {
            state = 1; /* so we will come back to "case 1" */
            return i;
            case 1:; /* resume control straight after the return */
        }
    }
}

他的文章后面还有一些内容,例如如何使用宏包装这套机制。如何使用__LINE__宏避免state被赋予相同的数值。如何避免多线程调用下干扰静态变量等等。我这里就不赘述了,大家有兴趣可参考原文。
总之读此文的两个收获一个是认识协程,一个是学习到了一种诡谲的C语言用法。非常开心。

年度读书计划-总结

去年11月份给自己定了一年的读书计划,结果可以说实现得非常糟糕。

决定当年读完的《linux内核》和《编译原理》两部书,都没有认真看完。只是概略翻了翻。

不过也不是说自己一点书都没有读。

图书馆借到的两本书,就看得津津有味。

一部是《linux设备驱动程序》,我一直都没找到合适的学习linux内核的书籍,才发现学习驱动是认识内核的一个非常好的切入点。而这一部书原理介绍的非常清晰,更容易从学习中抓到内核脉络。

另一部是《C专家编程》。年少无知的我一直认为自己对C语言了解已经足够了,接下来不再需要阅读语言相关的书籍了。看了这部书之后我的看法大大转变。可以说学无止境这句话用不能忘吧。这部书是Solaris系统程序员所写,风趣生动,包含大量系统开发过程中遇到的实际案例,同时介绍了C 语言标准、编译链接等等众多细节和trick。看这本书那两天,我几乎一直在捧腹大笑。实在是好书。尤其这本书言谈中吐露出的黑客文化,对我也是再一次的激励。

说到黑客文化,这里强烈推荐的是emacs中附带的advanture文字冒险游戏。相信喜欢tbbt的同志们应该已经看到Sheldon玩过了,“动用全球最先进的图形渲染引擎,AKA 大脑”,;)

新的一年一定要努力读书了,否则书架上书看不完也带不走,那就太浪费了!!

新年第一篇

新年要早起哈~

好久没写博客了。。也许是因为忙吧,也许是因为沉迷游戏。。也许是因为不够细心了所以不像之前那样一直有新发现。。

总归新的一年一定会有新的感想和新的成绩的~

— \ | / — 莫名其妙的分割线 — \ | / —

n年没上豆瓣的人被人加了一下好友,结果又去逛了逛,然后就火星的发现了这个

是正在看某个电影的页面的时候,出现在页面右上的。

确实是个很不错的主意啊,把fm和电影整合在了一起。

呵呵,应该说豆瓣的那一群人还是蛮有意思的吧。

其实人类只是寂寞了

把在buzz上说的话贴过来了。。

认为外星生物非得是要水要温暖要氧气才能生存实在是太幼稚了。
为什么生物一定要是碳基或者磷基的?
为什么生物一定要是高分子的?
为什么生物一定要是化学的?
为什么生物一定要是在行星上的?

其实人类只是希望在外星找到自己罢了。
其实人类只是孤独罢了。

linux发烧

最近玩linux比较hi,虽然没有什么正式的成果,但还是很开心~

先说说最近的吧,终于下定狠心把ubuntu上的gdm关掉了。以后进linux就全面进入黑屏时代。但说实话不能上网搜索材料的话,光有命令行也没什么用处。。因此找了几个重要的工具~

  • w3m 命令行下的网页浏览器 可嵌入emacs
  • freetalk 支持jabber的聊天工具~可以gtalk啦~
  • dfbsee DirectFB See 使用framebuffer的图片浏览程序,貌似还能播放视频~

基本上我对日常学习工作的需要也就是这些了。本来在emacs上配Gnus, 虽然配好了,但是很难用,尤其是似乎不是很适应gmail的标签风格,经常是显示不出来新邮件。我尝试在gmail里面设置规则,给所有邮件都加上一个inbox标签,才丑陋地解决了这个问题,但是还是很难用的一个东西。尤其是附件下载的方式也很糟糕。

现在有了w3m,可以直接访问gmail了,就没有这个问题了。w3m对下载的支持也很棒~唯一的问题就是不支持css吧。不过在命令行下面主要看的是文本,就算有css支持又怎样…反正命令行下面也没有字体字号什么的设置。

我其实最原始的在命令行下面看网页的想法是直接用wget把网页下载下来,再用脚本解析之后取出内容放纯文本里面看。可是wget只能下载,没法填写表单发送请求之类。我本以为可以直接在路径后面加?=什么的让他支持,但似乎失败了。例如尝试


wget google.com/search?q=hello

就失败了。收到403 forbidden 但是直接


wget google.com

是成功的。不知道是为什么……是google主动封锁了这样的读取方式吗?不知有没有解。。

freetalk没什么说的啦,纯粹好东西,还支持文件传输(未测试),比某些版本的gtalk客户端都好用。(不过不支持语音就是了。。)测试过聊天之后,进gmail一查,果然聊天记录也存在邮箱里了。可谓完美。加上使用@rainux同学的twitter gtalk机器人twimeido,还能发推。当然,用w3m连dabr也行。用不了web推,因为js太复杂的关系。。在命令行模式下直接ssh连上墙外的主机就可以建立tunnel,之后就在墙外了很方便。

dfbsee是我折腾了最久的一个东西。一直没找到好的命令行下看图工具。后来查到zgv,没来得及尝试,又发现了这个dfbsee。网上对dfbsee的评论是,因为使用splash的关系,很可能你的机器已经启用了framebuffer,那么为了看图去装另外一个图形驱动来运行zgv就不划算了。因为顾名思义,dfbsee是建立在fb上的看图工具。

ubuntu上没找到dfbsee的支持包,直接去官网下了源码。然后编译。要先编译安装DirectFB的代码库,很简单configure – make – make install就行了。然后就遇到问题了。官网下载的最新版的dfbsee源码和最新的dfb库居然是不兼容的,某个叫做DFBCardCapbilities的结构(后来发现貌似是个enum)找不到。网上搜了搜找到某个邮件列表里的讨论,原来开发人员把这个接口改名叫做DFBGraphicsDeviceDescription了,而dfbsee似乎还没来得及更新。按照他说的搞了个全文替换,要改这个结构还有一个get函数名。继续编译还有问题,有个叫做rotate.c的代码里面有很多形如


void * d;
(__u8*)d = ...;

的代码。首先那啥__u8,__u16,__u32之类的缺少定义,搞个typedef就行了,当然是对应的uchar,ushort和uint。接着那个类型强转在gcc眼里不算是lvalue,不能赋值。只好弄了个临时变量,中间倒腾了一下就行了。


void *d, __u8 *ptemp;
ptemp = (__u8*)d;
ptemp = ...;
d = (void*)ptemp;

其实也许改改编译选项也就过去了,但是实在懒得改就这么乱动代码蒙混过关了。编完了居然还不能跑,原来是某些so库它默认的路径和安装的路径不一样。用whereis找到那些库,然后ln -s 直接在对应地址下面建立符号链接,总算能跑了。其实本应该在configure的时候设置正确的路径的,但是实在懒得折腾了,就这样再次蒙混过关了。

这么折腾了半天之后这劳什字终于跑起来了。运行起来看看果然没白花时间。我的framebuffer设的1024*768*16bit,显示那些下载下来的墙纸什么的都很完美。还能一定程度地缩放。惟一缺点就是键盘处理有点问题,似乎是把按下一个键和抬起一个键当作两个事件处理了,结果按一次pagedown它要往后跳两幅图片。我一开始还以为文件夹里面有一半的图片他现实不了呢!后来用了他的slide-show功能,看到全部图片了~

不知道能不能把dfbsee设置成w3m的图片显示器,那样就太完美啦~不过这个以后再考虑吧。。

现在终极问题就是纯命令行模式下打不了中文。。这个实在不爽。看google就用英文当然也就算了,但是给人回信总不能总卖弄外国语吧囧。。。别跟我提zhcon,那东西太难用了。当然显示中文终于勉强能显示了。但是打字打不进去。打进去也是乱码。。况且现在我起zhcon只能sudo zhcon –utf8,结果是在zhcon里面搞的东西权限全是root的。这个很不爽。还要去弄那些中文字体的权限才能让zhcon不必跑在root下面,又要花时间折腾啊。。我准备再找找看有没有别的中文命令行,再试试看。最好是也是支持framebuffer的。“自己写一个”。。偶尔也会跳出这种想法。。但是最近这么忙不可能有时间折腾了。自己编译的内核还没跑成功过,正在试着写的linux驱动模块也有不少问题要调试。。所以其他想法先往后排吧。。

所以假如最近一段时间我给大家回邮件或者gt聊天总是跳英文,请不要生气。。我不是在卖弄英文。。。是懒得startx。。或者是正在备考12月底的英语机考。。妈的那玩意考不过就得明年重修。。抱歉爆粗口了囧|||

说到编译linux内核和学写linux驱动这回事,还有几句话想说。以前一直没想过要先写驱动。总觉得要先好好学会linux内核之后,在去学写驱动。结果买了些讲内核甚至讲源代码的书。不是说看不懂,而是说不知看来干嘛,有种无从下手的感觉。结果就晾在那里了。现在学写驱动,发现其实写驱动是学习内核的最佳手段,严重向大家推荐。这是一个很好的切入点,让你有事做,有一个目标,能去实践一些事情,然后你会遇到问题,就会想要做调试(当然就是内核调试),然后就会熟悉很多很多东西。现在我学的还浅,只知道些Oops啦panic啦什么的,strace也是刚刚学会用。接下来要试着弄user mode linux(这个也叫UML哈哈), 试着弄xen,还要试着弄很多好玩的东西。相信能学会更多吧。目前只能用用virtualbox,在x下是个好东西~不过既然决定要争取不进x,那还是要试着弄点更高级的!

于是自己加嘞个油吧。。(这么非主流。。。@ @)

年度读书计划

读书明明是享受的,本来从不想把读书这种事情做成计划的。。。

十一回上海把寄存在同学那里的两箱书运回来了。大三的时候心血来潮,天天从书店往寝室运书,结果都看不完,去北京实习也不可能把书都运过去,毕业的时候看着一堆看不完的书,也舍不得卖舍不得扔,就想等读研时候好好看。。

本来觉得读研应该挺轻松的吧,应该有时间可以慢悠悠读读书看看小说吧…没想到项目这么紧,论文压力这么大,课程安排这么密。。时间真的是铺得满满的了,想要再拿出时间看书的话,实在是不得不制定计划了。。。现在必须在宁波把这堆书看个差不多了,剩下的书不能超过一个书包的容量。。否则以后运来运去要死人了。。

我看书的习惯是第一遍粗略翻看,第二遍细读感兴趣的章节。所以一本书看两遍才可以考虑扔不扔这个问题。下面的书单上依次放个优先级放个进度百分比,200%算是看完。做个单子自勉吧。。

理论类
Graph Theory 中度优先 0%
混沌与分形 不急不急 0%
自动机理论和计算导论 中度优先 0%
编译原理(龙书) 尽快看完 20%
算法
算法艺术与信息学竞赛(黑书) 中度优先 50%
数据结构与算法分析 中度优先 50%
语言
Effective and More Effective C++ 不急不急 160%
C陷阱与缺陷 不急不急 60%
C++对象模型 不急不急 120%
C++程序设计语言 不急不急 20%
系统
windows驱动开发 尽快看完 0%
深入理解Linux内核 尽快看完 20%
深入理解.Net 中度优先 0%
Windows核心编程 不急不急 160%
网络
TCP/IP协议栈 中度优先 20%
堆栈攻击 不急不急 60%
Google Hacks (快过时了) 60%
编程经验
编程珠玑 I, II 中度优先 160%
设计模式 不急不急 120%
人月神话 不急不急 0%
集体智慧编程 暂未上架 40%
历史
史记 看不完的 20%
三国志 看不完的 20%
伯罗奔尼撒战争史 中度优先 40%

呼。。这样看来,尽快看完的是 编译原理 和 windows驱动、Linux内核。
争取12月之前搞定!

代码交叉拷贝悖论

一直以来写代码会把自己逼到某种非常难受的死角,明明是非常类似的代码,却不能方便的剪切粘贴,也无法用重构代码的方式合理的解决问题。就是他恰好类似于数据库交叉表那种情况,好像冥冥中指定了他的复杂度就是n平方。。。

从自己写的一个scroll bar的类里面找了一段代码出来。

m_thumbCap->SetVOffset(m_Height);
m_thumb->SetVOffset(m_Height);
m_thumbBottom->SetVOffset(m_Height);

m_thumbCap->SetRotation(270);
m_thumb->SetRotation(270);
m_thumbBottom->SetRotation(270);

滚动条的thumb由三块Image组件构成,cap,body和bottom。滚动条的图片默认是纵向的,横向滚动条是将图片横过来用的。这一段是将滚动条设置为横向的时候,分别将三块图片的垂直位置设置为属性里设置的高度,并将他们的图片旋转270度来横向使用。

我想说的是,这块代码的两组语句之间有着高度对称性,却难以利用这个对称性高效的开发出来,反而费时费力。。很纠结。不知大家遇到这种情况没有。

m_thumbCap->SetVOffset(m_Height);
m_thumbCap->SetRotation(270);
m_thumb->SetVOffset(m_Height);
m_thumb->SetRotation(270);
m_thumbBottom->SetVOffset(m_Height);
m_thumbBottom->SetRotation(270);

这个例子比较幸运,恰好修改一下分组方式修改起来会比较方便。甚至可以写个函数将这两句话包装起来。但有时遇到的情况就没这么方便了。


int coordinaryValue;
if(m_Orientation == ScrollBar::vertical){
coordinaryValue = value / (double)(m_Max-m_Min+m_PageSize) * m_Height;
m_thumbCap->SetVOffset(coordinaryValue);
}else if(m_Orientation == ScrollBar::horizontal){
coordinaryValue = value / (double)(m_Max-m_Min+m_PageSize) * m_Width;
m_thumbCap->SetHOffset(coordinaryValue);
}
setImagePos();

这段代码根据滚动条是纵向的还是横向的重新计算thumb块的位置并更新图形显示。横向和纵向的计算方式雷同却不相同,复制之后仍需修改几个地方,假如更复杂些的话就会更麻烦。并且这里甚至找不到很好的包装函数的方式。且不说由于整个项目只有这里用到了这一小块代码,封装函数可能对于开发效率也没啥提高。

啊啊貌似举了两个例子都不是很要命的那种,一时找不到非常典型的例子了。不过假如以前遇到同类问题的话,应该会有印象吧。也有可能所有类似问题都有解决办法的只是我有时困西西的编程没有仔细想吧。欢迎砸我~

匿名代理访问paypal可能遭封号

前两天空间租期快到了,就用paypal去充钱。。结果今天居然接到paypal从上海打来的电话确认我的付款行为,吓了我一跳。
网上查了查,仔细想了想,原来付款的时候忘记关代理了。。

假如使用匿名代理登录paypal付款的话,他就会直接封paypal帐号了。幸好现在他办事人性化了不少,先电话确认,才动手,否则就麻烦了。

仔细通读了一下user agreement, 又发现了好几条可能注意不到的会被paypal封号的条款

xiii.Opening multiple accounts;
开多个账户

xvi.Name on the bank account associated with the PayPal account does not match the name on the PayPal account;
银行账户名和paypal账户名称不符。

xviii.Use of an anonymizing proxy;
使用匿名代理

xxvi.Logging in from a country not included on PayPal’s permitted countries list.
从不符合paypal允许的国家登录

看来凡是跟钱有关的事情都必须小心谨慎,并且“行不更名做不改姓”,否则出了问题讲都讲不清楚。

最后引用一下月光博客遇到的类似问题,供大家借鉴参考。PayPal帐号的冻结和解冻

10000小时的自勉

今天又读了励志文章。。说是无论任何职业,只需训练10000小时便可达到世界顶尖水平。

其实这个理论很早之前在《读者》上看到过,说是训练3000小时可达到专业水平,训练10000小时可达到世界级别。当时虽然看得热血沸腾,可惜很快就把这事情忘记了。

不管怎么说,莫名其妙的再次被点燃了。果然励志文章就是像吸毒一样有效啊。。

虽然从初中开始玩VB和TURBO C,到目前为止,总共练习过多少个小时编程呢?估计不多。上大学之前,每个月能练习几个小时就不错了。对什么都只是一知半解,并且只能在家长不在的时候偷着做些实际的coding。大学四年确实做了一些项目,不过大一大二其实还是混混为主,每天能有2小时的练习时间就差不多了。算上寒暑假之类,只能给算1小时每天。大三大四开始有加班加点干活的习惯了,并且假期的时间基本上也都慢慢利用起来了。 可以算每天工作2小时了吧。这样算来,到目前为止我在程序技能上的训练大概是2千小时左右。

所以今后什么打算呢?在北京实习的时候我发现每天工作4个小时是效率最高的。虽然在公司蹲着的时间可能要10个小时都不止,但实际上coding的时间其实差不多也就4个小时。除了自己开小差之外,还要开会,跟同事和boss交换意见汇报情况问问题或者解答问题,发邮件到其他部门询问接口变化或者bug修复情况等等等。。。总之我给自己定的要求不会太高,就是4个小时。

说实话,在学校读书这段时间,想要保持4小时工作时间还是很有困难的。毕竟白天还有课,还要应付考试和做作业。

假如白天上一整天课的话,从7点开始工作做到11点休息,是4个小时,不过考虑到开小差的情况,周末需增加工作量。也就是说,能满足每周工作28小时就可以了。根据我的课程表,一周有两天是全天课有三天是全天休息,于是工作计划定为2h 4h 4h 2h 6h 6h 6h 就差不多了。假如能坚持每天4小时工作5年的话,就能达到7000多个小时的训练量,加上大学积累的2000小时左右就能接近10000小时这个数字了。怎么样,想想就热血沸腾吧。。

不过这个目标是不可能做到的,因为这4个小时的训练,必须练习的是自己不熟悉的内容。而毕业之后进入公司,多半是重复劳动,不可能让你一直学新东西的。当然工作之余可以自己参与开源项目或者玩topcoder, 但精力有限是不可能做到足额时间的。因此这个计算要折半。假如我保持旺盛精力不断学习不断努力工作的话,10年之后就能接近10000小时的训练积累了。

接下来是内容。每天训练4个小时,做什么内容呢?五年这么长的时间是无法预计的,不过我可以考虑今年的目标。我现在手头有一个网站外包项目,一个RFID课程项目,一个uC/OS单片机移植项目,说起来都算是不熟悉的内容,在12月份之前完成,只需高密度工作就能保证训练时间了。之前的AIIDE比赛虽然结果很糟糕,但无论如何已经开始学习机器学习的知识了,接下来就是把他写成论文,先解决研究生毕业的问题,然后看看是否有其他的事情可以做,或者是否有兴趣相投的同学一道共同探讨。说到比赛,chaosBB同学成绩不错啊,这里先祝贺一下,有机会希望能跟你多交流切磋呢~

除了开发和工程的训练,另一方面就是领域知识和学科知识。帮老师做网站外包,看似只是苦力,但我认为这是了解商业领域知识的大好机会,尤其这次项目涉及到在线付款仓储物流等种种内容,能学到的东西应该不少。接下来我那个超级能忽悠的导师又接到或者正准备接好几个项目,有团购相关的,有物流平台相关的,能继续帮他做是情的话,对商务领域的知识学习一定是有帮助的。

至于学科知识,这就要靠个人修为了。本来打算在浙大这两年把zoj的题好好刷一刷,谁知道一进校门项目就这么紧,并且在校时间缩短成不到一年,估计是没时间练习了。不过技术文章一直都在看,对算法的理解慢慢在深化。我越来越发现,其实真正重要的不是掌握很多花哨的算法和数据结构,而是要对算法的基本常识理解得更加深入,对一些最基本最简单的算法要有更深刻的理解和认识。最初学算法的时候,我对线性结构和顺序访问不屑一顾。觉得二叉树啦堆啦快排啦什么的多牛逼(嗤之以鼻的同学请自行替换成线段树、斐波那契堆、哈希等等更高级物。。。),为何还要看这些几乎算不上算法的算法。可是经过几年时间慢慢熟悉了这些东西,最初那种对高级算法的新奇劲过去了,才发现其实朴素才是美。所谓kiss才是真。归根结底,真正常用、强大而有效的数据结构只有三种,数组,链表和堆栈。而数组又是基本的基本。很多高深的算法,其实只是更加高效的利用数组;很多神奇的数据结构,无非也是以某种顺序存储在数组里。这也是更多小时的训练带来的效果吧。以此而论即使没有更多的时间去刷zoj,能把对算法的理解领悟一点一点用到工程中去并且不断精雕细琢的话,算法的修为一定能有长进的吧。实习期间做的恰好是性能分析,感觉对复杂度、性能瓶颈等问题的嗅觉更敏感了,也写了一些小工具去分析代码质量。以后继续保持严谨的开发习惯的话,这些技能的提高应该能继续吧。

前一节讲的是算法,学科知识当然不只这个了,只不过这个是基础。此外必修课当然是操作系统和网络,好在学嵌入式学单片机学驱动开发学物联网,这些知识在校在职应该都会慢慢接触。至于机器学习作为我的研究兴趣点,在可预见的两三年内应该也会继续钻研。另外保持大量阅读技术类的新闻的话,也会磨砺自己的技能吧。因此每天花在reader上的2小时,绝不算是白费呢。

最后说说英文吧。假期里下载了一堆futurama,没有字幕,完全是裸看的。一开始很难熬,一集要反复看两三遍才看明白什么意思,才尴尬的找到已经笑不出的笑点。。。然慢慢看了几季之后,已经可以几乎不费力的理解大部分内容,并且确切知道生词出现在什么位置。甚至可以边看边给弟弟做翻译。。后来打星际,上战网看贴,感觉已经没有以前读英文的那种畏难情绪了。慢慢熟悉之后感觉读英文并不比读中文累多少,当然速度还比不上中文啦,毕竟有生词这个障碍。这就是更多小时的训练的反馈吧。以此作为激励,其他的事情也是一样的,只要坚持努力,一定会有好结果的~