当前位置:嗨网首页>书籍在线阅读

11-LinuxThreads

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

33.5.1 LinuxThreads

多年以来,LinuxThreads曾一直是Linux上的主流线程实现,也能够满足各种线程化应用程序实现的需要。LinuxThreads实现的要点如下。

  • 线程的创建使用了clone(),并指定有如下标志:
  • 这意味着,LinuxThreads线程共享虚拟内存、文件描述符、文件系统相关信息(umask,根目录和当前工作目录)以及信号处置。不过,线程间并不共享进程ID和父进程ID。
  • 除了由应用程序创建的线程以外,LinuxThreads还会创建一个附加的管理线程,负责处理其他线程的创建和终止。
  • LinuxThreads利用信号来处理内部的操作。对于支持实时信号的内核来说(Linux 2.2及以后版本),会使用头3个实时信号。对于老版本内核,则使用SIGUSR1和SIGUSR2。应用程序不能使用这些信号。(对于各种线程同步操作而言,使用信号会导致较高延迟。)

LinuxThreds对标准行为的背离之处

LinuxThreads在很多方面与SUSv3 Pthreads标准并不一致。(LinuxThreads实现受限于其开发时可用的内核特性,而在此范围内,则会尽量保持一致。)以下列表对背离之处作了概述。

  • 在同一进程的不同线程中调用getpid()会返回不同的值。调用getppid()则反应了如下事实:除了主线程以外的所有线程都由进程的管理线程创建(getppid()会返回管理线程的进程ID)。在主线程和其他线程中,对getppid()调用的返回值相同。
  • 如果某线程调用fork()创建了一子进程,那么任何其他线程都可使用 wait()或类似技术来获取子进程的终止状态。不过,事实并非如此,只有创建子进程的线程才能使用wait()。
  • 如果某线程执行了 exec(),那么按照 SUSv3 要求,将终止所有其他线程。不过,只要调用exec()的是主线程之外的线程,那么产生进程则与调用线程拥有相同的进程ID,而与主线程的进程ID不同。而依照SUSv3标准,该进程ID应与主线程的进程ID保持一致。
  • 线程之间不会共享凭证(用户ID与用户组ID)。当一多线程进程执行一个set- user- ID程序时,将导致线程之间无法通过pthread_kill()来发送信号,因为这两个线程的凭证已经发生了改变,发送线程不再有权发信号给目标线程(请参考图20-2)。另外,由于LinuxThreads实现在内部使用了信号,一旦线程改变了自身的凭证,那么各种Pthreads操作有可能失败或者挂起(hang)。
  • 还未能顾及SUSv3关于线程与信号间交互规范的各个方面。
    • 采用kill()或者sigqueue()向某进程发送的信号,应该由目标进程中不阻塞该信号的任意线程来接收和处理。不过,因为LinuxThreads线程的进程ID不同,所以只能将信号送给特定线程。要是该线程阻塞这一信号,即使其他线程并不阻塞此信号,它也会一直保持挂起(pending)。
    • LinuxThreads并不支持信号为整个进程挂起的概念,只支持每个线程分别挂起信号。
    • 如果信号针对包含某个多线程应用的进程组,那么应用中的所有线程(即每个创建了信号处理函数的线程)都会处理该信号,而不是由(任意)某个线程去处理。例如,键入某个终端字符就会产生针对前台进程组的任务控制(job-control)信号。
    • 背选信号栈设置(由sigaltstack()建立)是针对每个线程的。不过,如果新线程不慎从pthread_create()调用者那里继承了备选信号栈设置,那么这两个线程将共享同一备选信号栈。SUSv3规定新线程启动时不应定义备选信号栈。LinuxThreads这一“违规”操作的后果是,如果两个线程恰巧在它们共享的备选信号栈中同时处理不同的信号,很可能会导致混乱(例如,程序崩溃)。这一问题可能非常难以重现,也很难调试,因为问题的出现依赖于在同一时点处理两个信号,这种情况极其罕见。

在使用LinuxThreads的程序中,新线程可以通过调用sigaltstack()来确保使用与其创建者线程不同的备选信号栈(或许根本就没有栈)。不过,可移植程序(以及创建线程的库函数)并不知道这些,因为在其他实现上这并非必须。另外,即使采用这种技术,依然可能产生竞争条件:新线程在有机会调用sigaltstack()之前依然有可能接收并处理备选栈上的信号。

  • 线程不共享一般的任务号以及进程组号。不能利用setsid()和setpgid()系统调用去改变多线程程序中的会话号以及进程组号。
  • 使用fcntl()建立的记录锁也不能共享。重复地对同一类型的锁的请求是无法合并在一起执行的。
  • 线程不共享资源的限制。SUSv3定义资源限制是一种进程范围的属性。
  • 函数times()返回的CPU时间以及由getrusage()返回的资源使用信息也都是针对每一个线程的。这些系统调用应该返回这个进程的总量。
  • 一些版本的 ps(1)会显示进程中的所有线程(包括管理线程),作为单独的项,且它们的进程号也不相同。
  • 线程之间并不共享由setpiority()设置的nice值。
  • 使用setitimer()创建的间隔定时器也无法在线程之间共享。
  • 线程之间不能共享System V信号量还原(semadj)值。

LinuxThreads的其他问题

除了以上与SUSv3标准的偏差外,LinuxThreads实现还有如下问题。

  • 如果管理线程被杀掉,那么余下的线程只能手工清理。
  • 多线程程序的核心转储(core dump)可能并不包含所有的线程(甚至可能也不包含触发转储的线程)。
  • 只有从主线程调用非标准的ioctl()TIOCNOTTY操作才能移除进程与控制终端的关联。