Solrex's profileSolrex Shuffling.LifeBlogListsNetwork Tools Help

Blog


    3/30/2008

    New Sub-domains of solrex.cn

    由于对某些服务感到新鲜,所以为我的域名添加了几个子域名:

    1. http://m.solrex.cnhttp://wap.solrex.cn

    我的手机版博客,使用 WireNode 提供的服务,上面两个域名均重定向到 http://solrex.wirenode.mobi,我用自己的手机访问测试正常。

    2. http://feed.solrex.cnhttp://feeds.solrex.cn/solrex

    将博客 FeedSkyFeedBurner 烧录绑定到了自己的域名,不影响原有的 feed 地址。

    其中 http://feed.solrex.cnFeedSky 提供的域名绑定服务,http://feeds.solrex.cn/solrexFeedBurner 提供的 MyBrand 服务,这两个域名均为直接 CNAME 绑定。所以,FeedBurner 那个在国内仍然无法正常访问,不过可以通过阅读器订阅。

    3/19/2008

    Miserable Days

    看着早晨天上弥漫的沙尘,我本来期望着今天会有一场沙尘暴,来加深一下北京奥运这一年给我留下的美好回忆。不过不幸的是,老天似乎不乐意做点什么让这一年显得更与众不同。

    是的,这一年没那么美好。现在我面前放着一个圆形的不锈钢饭盒,里面还留着一些晚饭吃方便面留下的残渣。桌子下面扔着一个曾经装满零食现在只剩下豆 奶粉和榨菜的大塑料袋,柜子里堆满了超市小票银行签单等乱七八糟的东西。一堆没洗的袜子,脱下来的厚衣服也只是挂了起来,唉,看起来一团糟。

    有个朋友在小百合上问我,最近干嘛呢?是啊,最近干嘛呢?我说我主要在看 Desperate Housewives,顺便听听课,看看书,写写博客。这的确是实话,在不到一个星期的时间里,我看完了 3 个 season(有人评论说这不像是男人的作风)。不过还没有提某天玩三国 11 一直到早上五点(虽然再看到它都有生理反应了),还有一堆从 FTP down 下来的电影。

    我总是在别人面前试图表现出来积极向上的样子,让别人说,啊哈,看这家伙!但其实这家伙很虚伪和愚蠢。Gosh,我怎么能做那种浪费时间浪费生命挥霍健康的事呢?不过我确实做了!

    没有朋友,没有运动,没有活动,没有美食,我大概体验到了研究生院的美好生活,这还不包括即将到来的论文的压力。当我做决定要读研究生时,我真的憧 憬这段时间将是 happy learning time,轻松地看看书,做做自己感兴趣的研究,像所有期待美好生活的人一样,我失望了。这真是一段糟糕的生活,各种压力,各种沮丧!

    这时我居然听到笔记本电脑发出"唧唧"的声音,或许它也想说我有压力?

    在一堆糟糕的事情中寻找点能让人舒服的东西其实也不难,比如南京移动的这个活动,每答对一题送我 0.5 元话费,我想,或许我应该为某个体育盛会在北京召开感到开心。

    每个人都会做一些事情来摆脱困扰,我觉得,我应该多花些时间去南京陪陪我的 xixi,南京的三月应该比北京更生机勃勃吧!

    3/14/2008

    Linux Implementation of POSIX Thread Cancellation Points


    POSIX 线程取消点的 Linux 实现

    作者:Wenbo Yang <http://solrex.cn>
    原文链接:http://blog.solrex.cn/articles/linux-implementation-of-posix-thread-cancellation-points.html

    摘要:

    这篇文章主要从一个 Linux 下一个 pthread_cancel 函数引起的多线程死锁小例子出发来说明 Linux 系统对 POSIX 线程取消点的实现方式,以及如何避免因此产生的线程死锁。

    目录:

    1. 一个 pthread_cancel 引起的线程死锁小例子
    2. 取消点(Cancellation Point)
    3. 取消类型(Cancellation Type)
    4. Linux 的取消点实现
    5. 对示例函数进入死锁的解释
    6. 如何避免因此产生的死锁
    7. 结论
    8. 参考文献

    1. 一个 pthread_cancel 引起的线程死锁小例子

    下面是一段在 Linux 平台下能引起线程死锁的小例子。这个实例程序仅仅是使用了条件变量和互斥量进行一个简单的线程同步,thread0 首先启动,锁住互斥量 mutex,然后调用 pthread_cond_wait,它将线程 tid[0] 放在等待条件的线程列表上后,对 mutex 解锁。thread1 启动后等待 10 秒钟,此时 pthread_cond_wait 应该已经将 mutex 解锁,这时 tid[1] 线程锁住 mutex,然后广播信号唤醒 cond 等待条件的所有等待线程,之后解锁 mutex。当 mutex 解锁后,tid[0] 线程的 pthread_cond_wait 函数重新锁住 mutex 并返回,最后 tid[0] 再对 mutex 进行解锁。

    1  #include <pthread.h>

    3  pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    4  pthread_cond_t   cond = PTHREAD_COND_INITIALIZER;

    6  void* thread0(void* arg)
    7  {
    8    pthread_mutex_lock(&mutex);
    9    pthread_cond_wait(&cond, &mutex);
    10   pthread_mutex_unlock(&mutex);
    11   pthread_exit(NULL);
    12 }
    13
    14 void* thread1(void* arg)
    15 {
    16   sleep(10);
    17   pthread_mutex_lock(&mutex);
    18   pthread_cond_broadcast(&cond);
    19   pthread_mutex_unlock(&mutex);
    20   pthread_exit(NULL);
    21 }

    22 int main()
    23 {
    24   pthread_t tid[2];
    25   if (pthread_create(&tid[0], NULL, &thread0, NULL) != 0) {
    26     exit(1);
    27   }
    28   if (pthread_create(&tid[1], NULL, &thread1, NULL) != 0) {
    29     exit(1);
    30   }
    31   sleep(5);
    32   pthread_cancel(tid[0]);
    33
    34   pthread_join(tid[0], NULL);
    35   pthread_join(tid[1], NULL);
    36
    37   pthread_mutex_destroy(&mutex);
    38   pthread_cond_destroy(&cond);
    39   return 0;
    40 }

    看起来似乎没有什么问题,但是 main 函数调用了一个 pthread_cancel 来取消 tid[0] 线程。上面程序编译后运行时会发生无法终止情况,看起来像是 pthread_cancel 将 tid[0] 取消时没有执行 pthread_mutex_unlock 函数,这样 mutex 就被永远锁住,线程 tid[1] 也陷入无休止的等待中。事实是这样吗?

    2. 取消点(Cancellation Point)

    要注意的是 pthread_cancel 调用并不等待线程终止,它只提出请求。线程在取消请求(pthread_cancel)发出后会继续运行,直到到达 某个取消点(Cancellation Point)。取消点是线程检查是否被取消并按照请求进行动作的一个位置。pthread_cancel manual 说以下几个 POSIX 线程函数是取消点:
           pthread_join(3)
           pthread_cond_wait(3)
           pthread_cond_timedwait(3)
           pthread_testcancel(3)
           sem_wait(3)
           sigwait(3)
    在中间我们可以找到 pthread_cond_wait 就是取消点之一。

    但是,令人迷惑不解的是,所有介绍 Cancellation Points 的文章都仅仅说,当线程被取消后,将继续运行到取消点并发生取消动作。但我们注意到上面例子中 pthread_cancel 前面 main 函数已经 sleep 了 5 秒,那么在 pthread_cancel 被调用时,thread0 到底运行到 pthread_cond_wait 没有?

    如果 thread0 运行到了 pthread_cond_wait,那么照上面的说法,它应该继续运行到下一个取消点并发生取消动作,而后面并没有取消点,所以 thread0 应该运行到 pthread_exit 并结束,这时 mutex 就会被解锁,这样就不应该发生死锁啊。

    3. 取消类型(Cancellation Type)

    我们会发现,通常的说法:某某函数是 Cancellation Points,这种方法是容易令人混淆的。因为函数的执行是一个时间过程,而不是一个时间点。其实真正的 Cancellation Points 只是在这些函数中 Cancellation Type 被修改为 PHREAD_CANCEL_ASYNCHRONOUS 和修改回 PTHREAD_CANCEL_DEFERRED 中间的一段时间。

    POSIX 的取消类型有两种,一种是延迟取消(PTHREAD_CANCEL_DEFERRED),这是系统默认的取消类型,即在线程到达取消点之前,不会出现真正的取消;另外一种是异步取消(PHREAD_CANCEL_ASYNCHRONOUS),使用异步取消时,线程可以在任意时间取消。

    4. Linux 的取消点实现

    下面我们看 Linux 是如何实现取消点的。(其实这个准确点儿应该说是 GNU 取消点实现,因为 pthread 库是实现在 glibc 中的。) 我们现在在 Linux 下使用的 pthread 库其实被替换成了 NPTL,被包含在 glibc 库中。

    以 pthread_cond_wait 为例,glibc-2.6/nptl/pthread_cond_wait.c 中:
    145      /* Enable asynchronous cancellation.  Required by the standard.  */
    146      cbuffer.oldtype = __pthread_enable_asynccancel ();
    147
    148      /* Wait until woken by signal or broadcast.  */
    149      lll_futex_wait (&cond->__data.__futex, futex_val);
    150
    151      /* Disable asynchronous cancellation.  */
    152      __pthread_disable_asynccancel (cbuffer.oldtype);

    我们可以看到,在线程进入等待之前,pthread_cond_wait 先将线程取消类型设置为异步取消(__pthread_enable_asynccancel),当线程被唤醒时,线程取消类型被修改回延迟取消 __pthread_disable_asynccancel 。

    这就意味着,所有在 __pthread_enable_asynccancel 之前接收到的取消请求都会等待 __pthread_enable_asynccancel 执行之后进行处理,所有在 __pthread_disable_asynccancel 之前接收到的请求都会在 __pthread_disable_asynccancel 之前被处理,所以真正的 Cancellation Point 是在这两点之间的一段时间。

    5. 对示例函数进入死锁的解释

    当 main 函数中调用 pthread_cancel 前,thread0 已经进入了 pthread_cond_wait 函数并将自己列入等待条件的线程列表中(lll_futex_wait)。这个可以通过 GDB 在各个函数上设置断点来验证。

    当 pthread_cancel 被调用时,tid[0] 线程仍在等待,取消请求发生在 __pthread_disable_asynccancel 前,所以会被立即响应。但是 pthread_cond_wait 为注册了一个线程清理程序(glibc-2.6/nptl/pthread_cond_wait.c):
    126  /* Before we block we enable cancellation.  Therefore we have to
    127     install a cancellation handler.  */
    128  __pthread_cleanup_push (&buffer, __condvar_cleanup, &cbuffer);

    那么这个线程清理程序 __condvar_cleanup 干了什么事情呢?我们可以注意到在它的实现最后(glibc-2.6/nptl/pthread_cond_wait.c):
    85  /* Get the mutex before returning unless asynchronous cancellation
    86     is in effect.  */
    87  __pthread_mutex_cond_lock (cbuffer->mutex);
    88}

    哦,__condvar_cleanup 在最后将 mutex 重新锁上了。而这时候 thread1 还在休眠(sleep(10)),等它醒来时,mutex 将会永远被锁住,这就是为什么 thread1 陷入无休止的阻塞中。

    6. 如何避免因此产生的死锁

    由于线程清理函数 pthread_cleanup_push 使用的策略是先进后出(FILO),那么我们可以在 pthread_cond_wait 函数前先注册一个线程处理函数:
    void cleanup(void *arg)
    {
            pthread_mutex_unlock(&mutex);
    }
    void* thread0(void* arg)
    {
      pthread_cleanup_push(cleanup, NULL);  // thread cleanup handler
      pthread_mutex_lock(&mutex);
      pthread_cond_wait(&cond, &mutex);
      pthread_mutex_unlock(&mutex);
      pthread_cleanup_pop(0);
      pthread_exit(NULL);
    }

    这样,当线程被取消时,先执行 pthread_cond_wait 中注册的线程清理函数 __condvar_cleanup,将 mutex 锁上,再执行 thread0 中注册的线程处理函数 cleanup,将 mutex 解锁。这样就避免了死锁的发生。

    7. 结论

    多线程下的线程同步一直是一个让人很头痛的问题。POSIX 为了避免立即取消程序引起的资源占用问题而引入的 Cancellation Points 概念是一个非常好的设计,但是不合适的使用 pthread_cancel 仍然会引起线程同步的问题。了解 POSIX 线程取消点在 Linux 下的实现更有助于理解它的机制和有利于更好的应用这个机制。

    8. 参考文献

    [1] W. Richard Stevens, Stephen A. Rago: Advanced Programming in the UNIX Environment, 2nd Edition.
    [2] Linux Manpage
    3/9/2008

    Fetion Indicates That CMCC Is A Flirtatious Idiot


    飞信充分表明了中国移动是一个闷骚的弱智

    因为平时短信通信量很大,所以最近在尝试中国移动的飞信业务,然后发现了一些很有趣的事情。

    1. 不允许下载飞信手机客户端到电脑。

    在飞信的官方网站上,手机客户端只能用手机上网下载。(飞信给你发条包含 URL 的消息,你点击下载)

    为什么用户不能在电脑上下载了然后自己安装到手机上呢?嘿嘿,就算只有几十K,GPRS 下载也是有钱赚的(0.01元/KB那也是将近一块钱那)!要是大家都在电脑上下了,中国移动不少赚了很多吗?

    其实这是典型的中国移动拿用户当弱智耍的例子(当然,这也不是第一次了)。不过大家要明白一个道理,几乎所有 WAP 网站都是可以通过 WEB 访问的,那么移动给的链接自然可以用电脑下载的。拿我的 Samsung E908 来说,移动给的链接是:
    http://nav.m161.com.cn/drops/clientdownload.aspx?category=j2me&vendor=sam&model=e908
    在这里链接下载下来的是一个 Amigo_sam_e908.jad,和下载 JAVA 游戏时一样,.jad 文件其实只是一个文本文件,用文本编辑器打开后就能发现真正的 JAVA 程序 .jar 文件的下载地址是:
    http://nav.m161.com.cn/drops/clientdownload.aspx?category=j2me&vendor=sam&model=e908_jar
    在这个链接下载下来的是一个 Amigo_sam_e908.jar 文件,然后只需要把那个 .jad 文件中的 MIDlet-Jar-URL: 后面换成 .jar 文件的文件名 Amigo_sam_e908.jar,然后将两个文件传输到手机里,按照一般安装手机 JAVA 游戏的步骤安装即可。

    2. 手机客户端的常用短语。

    在用手机客户端发消息时,飞信内置了一些常用短语,下面大家来看看这些"常用"短语是什么:

    1> 你好。
    2> 有时间么?可以和你聊聊吗?
    3> 你好,很高兴可以和你成为朋友。
    4> 你多大了?是男生还是女生?
    5> 你是用手机聊吗?
    6> 为什么不说话?
    7> 你的名字好特别,是什么意思呢?
    8> 可以给我你的电话号码吗?
    9> 回头再聊,我正开车呢?
    10> 这么晚了,还不睡?

    看完了这几条,我实在不明白它们有哪条能勉强算上"常用"!特别是第 5 条和第 8 条,用飞信自然能看到对方在线状态和对方手机号码,傻啊,还问?

    嘿嘿,如果真的有某个人"常用"这几句话,我只能说你很"中国移动"了!
    3/3/2008

    Cross Stitch

    秀一下我给 xixi 绣的十字绣!莫笑我大男人做小女子事,幼时家贫,常着补丁衣,针线活计还是能做一点的。

    刚完成平面照

    完工立体照

    今天从卓越网购得《寂静的春天》一本,原在南大也曾想看过,但图书馆中仅藏两本旧书还都被借走,就有些小遗憾。不过没想到的是买到的这本居然是英文版加中文评注,真好玩,索性就当英文消遣读物好了。

    寂静的春天

    3/1/2008

    APUE, A Great Book

    这两周是选课试听期,还没有正式开始上课,所以有点空闲就翻了翻 UINX 环境高级编程(Advanced Programming in the UNIX Environment, 2e),看了七八章,发现这本书真的是无愧于"UNIX 编程圣经"的称号。书中对编程中可能遇到的问题讲解得非常系统和详细,尤其当看到自己以前遇到过问题的地方时,简直就有一种顿悟的感觉,就想感叹一句"哦,原来如此!"。

    我平常在写程序时,遇到问题总是求助于 Google。对那些讲编程技巧的书向来不怎么感冒(尤其是中国人写的),总觉得那种书根本不适合花时间仔细看一遍。这种问题驱动式的学习方式固然在解决某一特定问题时显得快捷高效,但是也往往受限于一叶障目不见泰山的困境。在解决了某一问题之后,对其它同类问题没有足够关注,导致再遇到类似问题时仍需要去搜索答案。

    问题驱动式的学习方式会导致对问题的了解不够系统和深入,但如果仅仅拿本大部头慢慢翻完的话,又会枯燥无味,而且体会也不深。我觉得读编程书的最好方法就是,先有一定量的实践,再去看书,而且要保持对书中习题和代码的练习量。有时候不妨先看实例代码再看正文解释,如果代码看得懂,看作者的解释是否和自己理解一样;如果代码看不懂,就会加深对正文的注意度。而且有时候读那些入门级的教科书,不妨只看代码。

    当然,在编程的时候,桌子上应该有几本经典图书当作手册来参考,不时地重读一下某些章节会很有好处。像 APUE,我就觉得非常适合作为案头书,做 Linux/Unix 开发的程序员买一本看看绝对不会失望。

    Google Talk Chatback Badge

    无意间发现 Google 推出了一个新功能,Google Talk 小徽章,把一个 iframe 放到网站上,这样网站的访客就能直接通过点击这个小徽章里的链接匿名和发布者聊天,不需要 Google 帐户。

    测试了一把,发现当状态标识为 "busy" 的时候,通过小徽章无法聊天;当状态标识为 "available" 时匿名访客才可以点击它来进行聊天。由于是匿名,聊天时发布者方显示的对方名字是 "Guest"。

    想看看效果?看我的个人主页,或者博客右侧 widgets。