Skip to content
xiehuc edited this page Apr 20, 2014 · 1 revision

理解异步设计

最早 lwqq 项目只提供同步方式,显然无法满足实际的使用需求,使得lwqq项目仅仅停留 在演示阶段,为了更大规模的使用,异步化是首先被提出的特性。所以我为此设计了异步 的部分,同时保持提供同步的兼容。需要说明的是,异步的设计也不是一蹴而就得,中途 经历过很多次的修改,也使得修改得越来越远,难于和上游同步,直到完成事件的设计, 提出vp_list.h才最后明朗起来,最后不得不分裂出来。成为一个独立的分支。不排除以后 重新合并进上游的可能,但是显然需要双方作出大量的修改。绝非易事

异步的设计分为许多层次,从底层到高层分别为:

  1. 事件循环库:如libev,libuv,glib的eventloop
  2. 封装接口层: async_impl.h
  3. 底层: LwqqAsyncTimer,LwqqAsyncIo
  4. 内层: req->do_request_async
  5. 高层: LwqqAsyncEvent

总体设计

为了减少互斥锁的使用,将所有的网络任务都单独放在一个线程中的事件循环中运行。其 它任务入回调函数则放在主线程的事件循环中运行。它们之间的交替是通过计时器来实现 的。(libev由于计时器不可重叠,是用pipe来触发的):

graph/two-threads.png

lwqq_async_dispatch 函数将为一个LwqqCommand添加一个很短的计时器,然后保证在 网络线程中执行, lc->dispatch 是一个函数指针,将为一个LwqqCommand添加一个很 短的计时器,然后保证在主线程中执行。因为主线程的事件循环是各种各样的,所以需要 自己提供实现 。覆盖默认提供的实现(默认提供的实现是 lwqq_async_dispatch 也就是网络任务和回调函数在同一线程中执行。

也许这里会产生疑问,放在同一线程中又怎么样了,哪里不行?实际上在命令行的程序中 并没有太大的问题。主要是为了兼容GUI程序,因为回调函数极有可能更新UI,而一些GUI 程序不提供线程安全的保障,比如gtk要求更新GUI的代码在主线程中执行。因此把所有的 回调都放在主线程中执行是可行的。而网络任务,使用的是curl库,虽然curl能够提供极 大程度的异步支持,但是DNS是同步的,在一些操作系统提供的curl开启了异步DNS的支持 ,更多的操作系统的curl的DNS操作则是同步的,如果把网络任务放在主线程中执行的话, 会导致卡UI,为此专门开辟一个新的线程,执行网络任务。

分层概述

事件循环库

事件循环库最初选择的是libev,因为它足够高效,同时因为我之前只接触过libev,并没 有很多的比较对象。事实上libev还是非常好用的。然而问题在于它的跨平台性不佳,使得 跨平台的时候遇到了不小的阻力。最后理解到要为了跨平台,不得不放弃libev开放选择, 所以后来选择了libuv,当然这个东西也不太好,至少我用着是这样感觉的,libuv明显的 设计上借鉴了libev的设计思路。而且是nodejs的一部分,使得跨平台性好一些。最后lwqq 库提供了两个默认选择,在linux上是libev,在windows上是libuv。当然,如果是需要其 它的事件循环库,只需要从async_impl.h中重新实现接口就可以了。比如lwqq的扩展 pidgin-lwqq项目选择了glib自带的eventloop作为默认选择,libev作为可选(linux)

async_impl.h

最早底层是直接和事件循环库相连的,通过宏扩展来切换库,当意识到需要开放这部分接 口的时候因此设计了async_impl.h,但同时也使得增加了一些性能消耗,实在是没有办法 的结果。

async_impl.h 只是包含一个头文件,它包括了一个结构体,成员都是函数指针。因此另外 写个c文件,将所有的函数都用选择的事件循环库实现一遍就完成了。调用 LWQQ_ASYNC_IMPLEMENT函数就选择了这个接口了。无论编译的时候是否开启了异步的支持 ,都会覆盖选择。

需要特别注意的是,实现中eventloop由于是使用的全局变量来保存的,所以只能创建一个 eventloop,这个循环专门用来运行网络请求。其它剩余的部分则交给主线程中的事件循环 来完成。

底层

因为有了接口,底层的实现基本上就交给了接口来实现了。大部分函数的内部都是 LWQQ__ASYNC_IMPL宏,通常是指的 lwqq_async_timer_watchlwqq_async_io_watch 这些函数。

内层

内层指的是不希望使用者知道部分,具体说来是 req->do_request_async 的参数中提 供的回调。在该回调中负责解析webqq服务器返回的json字符串,更新结构体,设置结果号 。

高层

高层是使用者需要直接使用的部分,也就是使用 lwqq_async_add_event_listener 这 部分,在回调函数中处理实际的变化,更新UI等等。

Clone this wiki locally