在路上...

The development of life
我们一直都在努力,有您的支持,将走得更远...

站内搜索: Google

  :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
     (主要是摘译Programming with POSIX threads)

如果想停止一个进程的主线程,而允许其他线程继续运行,必须使用pthread_exit(),而不是直接退出main()

 从第三方的角度上看,一个线程通过返回正常退出或者通过pthread_exit()退出,与通过取消机制结束执行的唯一区别就是被取消的线程返回值总是PTHREAD_CANCELLED

 一旦pthread_join()提取了结束线程的返回值,该线程就会被pthread_join()detach掉,并且可能在pthread_join()返回前就被操作系统回收掉了。这意味着,返回值不能是一个与结束线程栈有关的指针地址。

       动态初始化的pthread mutex,也需要调用pthread_mutex_destroy(),同样的道理也应用于pthread cond等。

       pthread_create()可能返回值EPERMEAGAINEINVAL*ENOMEM*

       首先,当你将一个条件变量用于多种事件时,你必须得使用broadcast,而不只是signal;其次,signal要比broadcast的效率要高。

       条件变量用于事件通知,而不是互斥访问。

       所有同时等待在同一个条件变量的线程,必须指定同一个mutex。但反之不然。

       在得到对应的mutex之后,和进入对某条件变量等待之前,务必再次等待条件进行检查。

       同样重要的还有:在线程被唤醒之后,也有再次测试唤醒条件。我们应该始终在一个循环内等待唤醒条件的成立,这样可安全地摆脱程序错误、多处理器竞界条件以及虚假(spurious)唤醒的困扰。

       虽然在等待一个条件变量之前必须持有对应的mutex,但可以在不持有对应mutex的情况下做执行条件变量的signal/broadcast。这样的优点是在许多系统上这么做的效果比较高。当休眠线程被唤醒之后,它一定要先锁住mutex。如果执行signal/broadcast的线程仍然锁住该mutex,这个刚被唤醒的线程就会再次立即进入休眠状态,从而我们要执行两次到休眠线程的上下文切换才能完成工作。

       但这样做的一个副作用就是,如果mutex没有被锁住,任何线程都可以在那个要被唤醒的线程之前锁住mutex。这种情况其实就是导致交叉(intercepted)唤醒的一个原因。例如,一个低优先级的线程可能在休眠中的高优先级线程之前得到处理器,从而延长高优先级线程的调度延迟。反之,如果mutex在做signal时是锁住的话,这种情况就不会发生,因为那个低优先级的线程也在休眠之中,而高优先级线程会先被调度(这个观点我还需要在Linux内核里寻找一些证据才能确定)。

      Pthread标准为"内存可见性"提供了少量基本限制。我们可以认为所有pthread实现都会遵守以下约定:

     1、原线程在调用pthread_create()时所能见到的内存值,新线程在开始时也能够见到。原线程在调用pthread_create()之后所写入的数据,新线程是不一定会看到的,甚至在是新线程开始之前就开始写入的数据。

     2、一个线程在解锁一个mutex所能见到的数据,另一个线程之后在锁住该mutex时也能够见到(要么直接锁住该mutex,要么是通过一个条件变量等待)。再次强调,在解锁unlock之后所写入的任何数据都不一定会被后来的线程所见到,甚至是在后者加锁之前的就已经开始写入的数据。

     3、在线程结束(要么取消、要从线程函数中返回、要么调用pthread_exit())时它所能见到的数据,都可以另一个线程在调用pthread_join()时见到。

     4、一个线程在执行条件变量signal/broadcast时所得见到的内存值,也会为对应唤醒线程所见到。再次强调,在signal/broadcast之后写入的数据,不一定会为唤醒线程所见到,即使它们在后者得到唤醒之前就开始写入了。

       内存访问在这种计算机内,至少是在概念上是在内存控制器中进行排队处理的,可能因为性能原因而调整它们处理顺序(当然是在保证正确性的前提下)。对一个不在CPU cache中的地址的读访问可能会一直等到cache填充之后才返回,即在其之后的读访问可能会先于它完成;写访问,可能要求flush旧数据,所以直到这个flush请求结束后才会返回,这样其之后的写访问也可能先于它完成。内存屏障的作用在于,确保在屏障之前启动的内存操作只会在屏障之后启动的内存操作完成之前完成。注意,内存屏障,不是一个cache flush命令,而可以想像它作为一个内存访问操作队列中的一个特殊成员,内存控制器不能删除它,也不能在屏障之前的操作完成之前处理屏障之后的任何操作。

       内存屏障是一堵移动的墙壁(moving wall),但不是“cache flush”命令。 一个常见的误解就是内存屏障会把值刷新到主存上,因而可以确保这些值对于其他处理器可见。但这个观点是错误的,内存屏障所保证的只是两个操作集合之间的顺序。如果认为每个内存访问是队列中一项的话,我们可以把内存屏障看作一个特殊的标记项。与其它内存访问操作不同,内存控制器不能删除屏障,或者越过它(直到它将完成屏障之前的所有访问操作)。更多的关于内存屏障的信息可以参考

      http://wiki.zh-kernel.org/doc/内存屏障

    (俺翻译的相当”草“,欢迎给予指教修正~)  

     PTHREAD_CANCEL_DEFERRED表示Pthread会在取消点检查是否有待处理的取消操作。很多取消点都在可能引起无固定时限等待的I/O操作上。如果发现一个取消操作等待处理,系统会直接开始调用清理函数,然后中断(terminate)线程。

     在使用异步取消的时候,不允许调用任何会获取资源的函数。实际上,此时只能调用那些标记为异步取消安全的函数,其中pthread函数中要求异步取消安全的函数只有: pthread_cancel()pthread_setcancelstate()pthread_setcanceltype()

     当一个线程被取消或者调用pthread_exit()退出时,Pthreads会按从最后增加的清理函数开始的顺序调用清理函数。当pthread_clean_pop()以非0值调用时,无论线程是否被取消,清理函数都会被执行。

     不能在一个函数里push一个 cleanup handler,而在另一个函数里pop它。因为一个pthread实现可能使用宏实现这两个操作,它们可能包括一对{}括号。不良的使用习惯会破坏代码的可移植性。

     设置一个TSB key的值为NULL(能过pthread_setsepecific),意味着这个key没有值,而不是值为NULL。如果一个正在结束(terminating)线程中的某个TSBkeyNULL,则pthreads不会对之调用TSB key的析构函数。另外,TSB key的析构函数在你替换现有key的时候也不会调用。

posted on 2009-08-27 21:24  palam  阅读(342)  评论(0编辑  收藏  举报