Linux中线程和进程的区别

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: Linux中线程和进程的区别 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,它是系统进行资源分配和调度的一个独立单位。例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格、内存空间、磁盘空间、I/O设备等,然后该进程被放入到进程的就绪队列,进程调度程序选中它,为它分配CPU及其他相关资源,该进程就被运行起来。




进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,它是系统进行资源分配和调度的一个独立单位。例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格、内存空间、磁盘空间、I/O设备等,然后该进程被放入到进程的就绪队列,进程调度程序选中它,为它分配CPU及其他相关资源,该进程就被运行起来。

线程是进程的一个实体,是CPU调度和分配的基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈),但是它可以与同属一个进程的其他的线程共享进程所拥有的全部资源。

在没有实现线程的操作系统中,进程既是资源分配的基本单位,又是调度的基本单位,它是系统中并发执行的单元。而在实现了线程的操作系统中,进程是资源分配的基本单位,而线程是调度的基本单位,是系统中并发执行的单元。

需要注意的是,尽管线程与进程很相似,但两者也存在着很大的不同,区别如下:

1)一个线程必定属于也只能属于一个进程;而一个进程可以拥有多个线程并且至少拥有一个线程。

2)属于一个进程的所有线程共享该线程的所有资源,包括打开的文件、创建的Socket等。不同的进程互相独立。

3)线程又被称为轻量级进程。进程有进程控制块,线程也有线程控制块。但线程控制块比进程控制块小得多。线程间切换代价小,进程间切换代价大。

4)进程是程序的一次执行,线程可以理解为程序中一个程序片段的执行。

5)每个进程都有独立的内存空间,而线程共享其所属进程的内存空间。

程序、进程与线程的区别见表6-1

6-1  程序、进程与线程区别

名称

描    述

程序

一组指令的有序结合,是静态的指令,是永久存在的。

进程

具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立  单元。进程的存在是暂时的,是一个动态概念。

线程

线程的一个实体,是CPU调度和的基本单元,是比进程更小的能独立运行的基本单元。本身基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)。一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。

简而言之,一个程序至少有一个进程,一个进程至少有一个线程。

1.定义

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位

线程是进程的一个实体,行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.是CPU调度和分派的基本单位,它是比进程更小的能独立运
2.关系
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
3.区别
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.

2) 线程的划分尺度小于进程,使得多线程程序的并发性高。

3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

4.优缺点

线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。


线程退出的条件:下面任意一个都可以。

1.调用pthread_exit函数退出。

2.其他线程调用pthread_cancel取消该线程,且该线程可被取消。

3.创建线程的进程退出或者整个函数结束。

4.当前线程代码执行完毕。

5.其中的一个线程执行exec类函数执行新的代码,替换当前进程所有地址空间。


当线程中休眠或者死循环时候,需要在住进程中调用pthread_join等待线程结束,死循环可以通过另外一个休眠的线程来结束,举例说明,让LCD显示摄像头数据,但是我们中途需要点击触摸屏来退出显示,视频显示是一个死循环来不停的读取视频数据,那么我们就可以创建两个线程,一个负责视频的不停读取,一个负责获取触摸屏数据,没有数据就休眠,当休眠被唤醒后就调用pthread_cancel取消死循环的线程,设计思路基本是这样。也可以采取进程实现这个操作。


线程与进程
为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题。

  使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。

  使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

  除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:

  1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。

  2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。

3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

 

一、线程标识

  • 线程有ID, 但不是系统唯一, 而是进程环境中唯一有效.
  • 线程的句柄是pthread_t类型, 该类型不能作为整数处理, 而是一个结构.

下面介绍两个函数:

  • 头文件:
  • 原型: int pthread_equal(pthread_t tid1, pthread_t tid2);
  • 返回值: 相等返回非0, 不相等返回0.
  • 说明: 比较两个线程ID是否相等.

 

  • 头文件:
  • 原型: pthread_t pthread_self();
  • 返回值: 返回调用线程的线程ID.

二、线程创建

 在执行中创建一个线程, 可以为该线程分配它需要做的工作(线程执行函数), 该线程共享进程的资源. 创建线程的函数pthread_create()

  • 头文件:
  • 原型: int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(start_rtn)(void), void *restrict arg);
  • 返回值: 成功则返回0, 否则返回错误编号.
  • 参数:
    • tidp: 指向新创建线程ID的变量, 作为函数的输出.
    • attr: 用于定制各种不同的线程属性, NULL为默认属性(见下).
    • start_rtn: 函数指针, 为线程开始执行的函数名.该函数可以返回一个void *类型的返回值,而这个返回值也可以是其他类型,并由 pthread_join()获取
    • arg: 函数的唯一无类型(void)指针参数, 如要传多个参数, 可以用结构封装.

linux下多线程程序的编译方法:

       因为pthread的库不是linux系统的库,所以在进行编译的时候要加上     -lpthread

       # gcc filename -lpthread  //默认情况下gcc使用c库,要使用额外的库要这样选择使用的库

三、线程属性

 pthread_create()中的attr参数是一个结构指针,结构中的元素分别对应着新线程的运行属性,主要包括以下几项:

 __detachstate,表示新线程是否与进程中其他线程脱离同步,如果置位则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到  PTHREAD_CREATE_JOINABLE状态。

 

__schedpolicy,表示新线程的调度策略,主要包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和  SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对超级用户有效。运行时可以用过  pthread_setschedparam()来改变。

 

__schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0。

 

__inheritsched,有两种值可供选择:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED。

 

 __scope,表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值:  PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。

 

pthread_attr_t结构中还有一些值,为了设置这些属性,POSIX定义了一系列属性设置函数,包括pthread_attr_init()、 pthread_attr_destroy()和与各个属性相关的pthread_attr_get(),pthread_attr_set()函数。

 

  pthread_create()中,第二个参数(pthread_attr_t)为将要创建的thread属性。通常情况下配置为NULL,使用缺省设置就可以了。但了解这些属性,有利于更好的理解thread.

属性对象(pthread_attr_t)是不透明的,而且不能通过赋值直接进行修改。系统提供了一组函数,用于初始化、配置和销毁每种对象类型。

 创建属性:

int pthread_attr_init(pthread_attr_t *attr);

创建的属性设定为缺省设置。

 销毁属性:

int pthread_attr_destroy(pthread_attr_t *attr);

 

一:设置分离状态:

线程的分离状态有2种:PTHREAD_CREATE_JOINABLE(非分离状态), PTHREAD_CREATE_DETACHED(分离状态)

分离状态含义如下:

如果使用 PTHREAD_CREATE_JOINABLE 创建非分离线程,则假设应用程序将等待线程完成。也就是说,程序将对线程执行 pthread_join。 非分离线程在终止后,必须要有一个线程用 join 来等待它。否则,不会释放该线程的资源以供新线程使用,而这通常会导致内存泄漏。因此,如果不希望线程被等待,请将该线程作为分离线程来创建。

 

如果使用 PTHREAD_CREATE_DETACHED 创建分离thread,则表明此thread在退出时会自动回收资源和thread ID.

 

Sam之前很喜欢使用分离thread. 但现在慢慢使用中觉得这样是个不好的习惯。因为分离thread有个问题:主程序退出时,很难确认子thread已经退出。只好使用全局变量来标明子thread已经正常退出了。

另外:不管创建分离还是非分离的thread.在子thread全部退出之前退出主程序都是很有风险的。如果主thread选择return,或者调用exit()退出,则所有thread都会被kill掉。这样很容易出错。Sam上次出的问题其实就是这个。但如果主thread只是调用pthread_exit().则仅主线程本身终止。进程及进程内的其他线程将继续存在。所有线程都已终止时,进程也将终止。

 

intpthread_attr_getdetachstate(const pthread_attr_t *attr,int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

得到当前和分离状态和设置当前的分离状态。

 

二:设置栈溢出保护区大小:

栈溢出概念:

·                     溢出保护可能会导致系统资源浪费。如果应用程序创建大量线程,并且已知这些线程永远不会溢出其栈,则可以关闭溢出保护区。通过关闭溢出保护区,可以节省系统资源。

·                     线程在栈上分配大型数据结构时,可能需要较大的溢出保护区来检测栈溢出。

int pthread_attr_getguardsize(const pthread_attr_t *restrictattr,size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr,size_t guardsize);

设置和得到栈溢出保护区。如果guardsize设为0。则表示不设置栈溢出保护区。guardsize 的值向上舍入为PAGESIZE 的倍数。

 

三:设置thread竞用范围:

竞用范围(PTHREAD_SCOPE_SYSTEM 或 PTHREAD_SCOPE_PROCESS)指 使用 PTHREAD_SCOPE_SYSTEM 时,此线程将与系统中的所有线程进行竞争。使用 PTHREAD_SCOPE_PROCESS 时,此线程将与进程中的其他线程进行竞争。

 

int pthread_attr_getscope(const pthread_attr_t *restrict attr,int*restrict contentionscope);
int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope);

 

四:设置线程并行级别:

int pthread_getconcurrency(void);
int pthread_setconcurrency(int new_level);

Sam不理解这个意思。

 

五:设置调度策略:

POSIX 标准指定 SCHED_FIFO(先入先出)、SCHED_RR(循环)或 SCHED_OTHER(实现定义的方法)的调度策略属性。

·                     SCHED_FIFO

如果调用进程具有有效的用户 ID 0,则争用范围为系统 (PTHREAD_SCOPE_SYSTEM) 的先入先出线程属于实时 (RT) 调度类。如果这些线程未被优先级更高的线程抢占,则会继续处理该线程,直到该线程放弃或阻塞为止。对于具有进程争用范围 (PTHREAD_SCOPE_PROCESS)) 的线程或其调用进程没有有效用户 ID 0 的线程,请使用 SCHED_FIFOSCHED_FIFO 基于 TS 调度类。

·                     SCHED_RR

如果调用进程具有有效的用户 ID 0,则争用范围为系统 (PTHREAD_SCOPE_SYSTEM)) 的循环线程属于实时 (RT) 调度类。如果这些线程未被优先级更高的线程抢占,并且这些线程没有放弃或阻塞,则在系统确定的时间段内将一直执行这些线程。对于具有进程争用范围 (PTHREAD_SCOPE_PROCESS) 的线程,请使用SCHED_RR(基于 TS 调度类)。此外,这些线程的调用进程没有有效的用户ID 0

SCHED_FIFO 是基于队列的调度程序,对于每个优先级都会使用不同的队列。SCHED_RR 与 FIFO 相似,不同的是前者的每个线程都有一个执行时间配额。

 

int pthread_attr_getschedpolicy(const pthread_attr_t *restrictattr,int *restrict policy);
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);

 

六:设置优先级:

int pthread_attr_getschedparam(const pthread_attr_t *restrictattr,struct sched_param *restrict param);

int pthread_attr_setschedparam(pthread_attr_t *restrict attr,
              conststruct sched_param *restrict param);

比较复杂,Sam没去研究。

 

七:设置栈大小:

当创建一个thread时,会给它分配一个栈空间,线程栈是从页边界开始的。任何指定的大小都被向上舍入到下一个页边界。不具备访问权限的页将被附加到栈的溢出端(第二项设置中设置)。

指定栈时,还应使用 PTHREAD_CREATE_JOINABLE 创建线程。在该线程的 pthread_join() 调用返回之前,不会释放该栈。在该线程终止之前,不会释放该线程的栈。了解这类线程是否已终止的唯一可靠方式是使用pthread_join

一般情况下,不需要为线程分配栈空间。系统会为每个线程的栈分配指定大小的虚拟内存。

#ulimit -a可以看到这个缺省大小

 四、线程终止

 

如果进程中的任一线程调用了exit,_Exit或者_exit,那么整个进程就会终止。与此类似,如果信号的默认动作是终止进程,那么,把该信号发送到线程会终止整个进程。

单个线程可以通过下列三种方式退出,在不终止整个进程的情况下停止它的控制流。

(1):从启动例程中返回,返回值是线程的退出码

(2):线程可以被同一进程中的其他线程取消

(3):线程调用pthread_exit()

pthread_exit函数:

  • 原型: void pthread_exit(void *rval_ptr);
  • 头文件:
  • 参数: rval_ptr是一个无类型指针, 指向线程的返回值存储变量.

 pthread_join函数:

  • 原型: int pthread_join(pthread_t thread, void **rval_ptr);
  • 头文件:
  • 返回值: 成功则返回0, 否则返回错误编号.
  • 参数:
    • thread: 线程ID.
    • rval_ptr: 指向返回值的指针(返回值也是个指针).
  • 说明:
    • 调用线程将一直阻塞, 直到指定的线程调用pthread_exit, 从启动例程返回或被取消.
    • 如果线程从它的启动例程返回, rval_ptr包含返回码.
    • 如果线程被取消, 由rval_ptr指定的内存单元置为: PTHREAD_CANCELED.
    • 如果对返回值不关心, 可把rval_ptr设为NULL.

线程取消的定义:

一般情况下,线程在其主体函数退出的时候会自动终止,但同时也可以因为接收到另一个线程发来的终止(取消)请求而强制终止。

 

线程取消的语义:

线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定。

线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出。

 

取消点定义:

根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、  pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是Cancelation-point;但CANCEL信号会使线程(http://blog.csdn.net/shanzhizi)从阻塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为Cancelation-point的系统调用前后调用  pthread_testcancel(),从而达到POSIX标准所要求的目标,即如下代码段:

pthread_testcancel();

 

   retcode = read(fd, buffer, length);

 

   pthread_testcancel(); 

 

程序设计方面的考虑:

如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用。即如下代码段:

While(1)

{

    ………

    pthread_testcancel();

}

 

与线程取消相关的pthread函数:

intpthread_cancel(pthread_t thread):线程可以通过调用pthread_cancel函数来请求取消同一进程中的其他进程。

发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。注意pthread_cancel并不等待线程终止,它仅仅提出请求。

 int pthread_setcancelstate(int state, int*oldstate):

设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和  PTHREAD_CANCEL_DISABLE,分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为 NULL则存入原来的Cancel状态以便恢复。

 

int pthread_setcanceltype(int type, int*oldtype)

设置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED和  PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出);oldtype如果不为NULL则存入原来的取消动作类型值。

 

void pthread_testcancel(void)

检查本线程是否处于Canceld状态,如果是,则进行取消动作,否则直接返回。

 http://blog.csdn.net/shanzhizi

线程可以安排它退出时需要调用的函数,这与进程可以用atexit函数安排进程退出时需要调用的函数是类似的。线程可以建立多个清理处理程序。处理程序记录在栈中,也就是说它们的执行顺序与它们注册时的顺序相反。

#include

void pthread_cleanup_push(void(*rtn)(void*),void *arg);

void pthread_cleanup_pop(int execute);

当线程执行以下动作时调用清理函数,调用参数为arg,清理函数的调用顺序用pthread_cleanup_push来安排。

调用pthread_exit

响应取消请求时

用非0的execute参数调用pthread_cleanup_pop时。

如果线程是通过从它的启动例程中返回而终止的话,那么它的清理处理程序就不会被调用,还要注意清理处理程序是按照与它们安装时相反的顺序被调用的。

 

int pthread_detach(pthread_t tid);

可以用于使线程进入分离状态。

 

线程属性标识符:pthread_attr_t 包含在 pthread.h 头文件中。

[c]   view plain  copy
  1. //线程属性结构如下:  
  2. typedef struct  
  3. {  
  4.     int                   etachstate;      //线程的分离状态  
  5.     int                   schedpolicy;     //线程调度策略  
  6.     structsched_param     schedparam;      //线程的调度参数  
  7.     int                   inheritsched;    //线程的继承性  
  8.     int                   scope;           //线程的作用域  
  9.     size_t                guardsize;       //线程栈末尾的警戒缓冲区大小  
  10.     int                   stackaddr_set;   //线程的栈设置  
  11.     void*                 stackaddr;       //线程栈的位置  
  12.     size_t                stacksize;       //线程栈的大小  
  13. }pthread_attr_t;  

属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。之后须用pthread_attr_destroy函数来释放资源。线程属性主要包括如下属性:作用域(scope)、栈尺寸(stack size)、栈地址(stack address)、优先级(priority)、分离的状态(detached state)、调度策略和参数(scheduling policy and parameters)。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。

一、线程的作用域(scope)

作用域属性描述特定线程将与哪些线程竞争资源。线程可以在两种竞争域内竞争资源:

  1. 进程域(process scope):与同一进程内的其他线程。
  2. 系统域(system scope):与系统中的所有线程。一个具有系统域的线程将与整个系统中所有具有系统域的线程按照优先级竞争处理器资源,进行调度。
  3. Solaris系统,实际上,从 Solaris 9 发行版开始,系统就不再区分这两个范围。

二、线程的绑定状态(binding state)

轻进程(LWP:Light Weight Process)关于线程的绑定,牵涉到另外一个概念:轻进程(LWP:Light Weight Process):轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。

  1. 非绑定状态
    默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。
  2. 绑定状态
    绑定状况下,则顾名思义,即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。

三、线程的分离状态(detached state)

  1. 线程的分离状态决定一个线程以什么样的方式来终止自己。
  2. 非分离状态
    线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
  3. 分离状态
    分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态。
  4. 线程分离状态的函数:pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。
    第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)。
    这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。

四、线程的优先级(priority)

  1. 新线程的优先级为默认为0。
  2. 新线程不继承父线程调度优先级(PTHREAD_EXPLICIT_SCHED)
  3. 仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0。

五、线程的栈地址(stack address)

  1. POSIX.1定义了两个常量_POSIX_THREAD_ATTR_STACKADDR 和_POSIX_THREAD_ATTR_STACKSIZE检测系统是否支持栈属性。
  2. 也可以给sysconf函数传递_SC_THREAD_ATTR_STACKADDR或 _SC_THREAD_ATTR_STACKSIZE来进行检测。
  3. 当进程栈地址空间不够用时,指定新建线程使用由malloc分配的空间作为自己的栈空间。通过pthread_attr_setstackaddr和pthread_attr_getstackaddr两个函数分别设置和获取线程的栈地址。传给pthread_attr_setstackaddr函数的地址是缓冲区的低地址(不一定是栈的开始地址,栈可能从高地址往低地址增长)。

六、线程的栈大小(stack size)

  1. 当系统中有很多线程时,可能需要减小每个线程栈的默认大小,防止进程的地址空间不够用
  2. 当线程调用的函数会分配很大的局部变量或者函数调用层次很深时,可能需要增大线程栈的默认大小。
  3. 函数pthread_attr_getstacksize和 pthread_attr_setstacksize提供设置。

七、线程的栈保护区大小(stack guard size)

  1. 在线程栈顶留出一段空间,防止栈溢出。
  2. 当栈指针进入这段保护区时,系统会发出错误,通常是发送信号给线程。
  3. 该属性默认值是PAGESIZE大小,该属性被设置时,系统会自动将该属性大小补齐为页大小的整数倍。
  4. 当改变栈地址属性时,栈保护区大小通常清零。

八、线程的调度策略(schedpolicy)

POSIX标准指定了三种调度策略:先入先出策略 (SCHED_FIFO)、循环策略 (SCHED_RR) 和自定义策略 (SCHED_OTHER)。SCHED_FIFO 是基于队列的调度程序,对于每个优先级都会使用不同的队列。SCHED_RR 与 FIFO 相似,不同的是前者的每个线程都有一个执行时间配额。SCHED_FIFO 和 SCHED_RR 是对 POSIX Realtime 的扩展。SCHED_OTHER 是缺省的调度策略。

  1. 新线程默认使用 SCHED_OTHER 调度策略。线程一旦开始运行,直到被抢占或者直到线程阻塞或停止为止。
  2. SCHED_FIFO
    如果调用进程具有有效的用户 ID 0,则争用范围为系统 (PTHREAD_SCOPE_SYSTEM) 的先入先出线程属于实时 (RT) 调度类。如果这些线程未被优先级更高的线程抢占,则会继续处理该线程,直到该线程放弃或阻塞为止。对于具有进程争用范围 (PTHREAD_SCOPE_PROCESS)) 的线程或其调用进程没有有效用户 ID 0 的线程,请使用 SCHED_FIFO,SCHED_FIFO 基于 TS 调度类。
  3. SCHED_RR
    如果调用进程具有有效的用户 ID 0,则争用范围为系统 (PTHREAD_SCOPE_SYSTEM)) 的循环线程属于实时 (RT) 调度类。如果这些线程未被优先级更高的线程抢占,并且这些线程没有放弃或阻塞,则在系统确定的时间段内将一直执行这些线程。对于具有进程争用范围 (PTHREAD_SCOPE_PROCESS) 的线程,请使用 SCHED_RR(基于 TS 调度类)。此外,这些线程的调用进程没有有效的用户 ID 0。

九、线程并行级别(concurrency)

应用程序使用 pthread_setconcurrency() 通知系统其所需的并发级别。

linux线程的实现

首先从OS设计原理上阐明三种线程:内核线程、轻量级进程、用户线程

内核线程

内核线程就是内核的分身,一个分身可以处理一件特定事情。这在处理异步事件如异步IO时特别有用。内核线程的使用是廉价的,唯一使用的资源就是内核栈和上下文切换时保存寄存器的空间。支持多线程的内核叫做多线程内核(Multi-Threads kernel )。

轻量级进程

轻量级线程(LWP)是一种由内核支持的用户线程。它是基于内核线程的高级抽象,因此只有先支持内核线程,才能有LWP。每一个进程有一个或多个LWPs,每个LWP由一个内核线程支持。这种模型实际上就是恐龙书上所提到的一对一线程模型。在这种实现的操作系统中,LWP就是用户线程。

由于每个LWP都与一个特定的内核线程关联,因此每个LWP都是一个独立的线程调度单元。即使有一个LWP在系统调用中阻塞,也不会影响整个进程的执行。

轻量级进程具有局限性。首先,大多数LWP的操作,如建立、析构以及同步,都需要进行系统调用。系统调用的代价相对较高:需要在user mode和kernel mode中切换。其次,每个LWP都需要有一个内核线程支持,因此LWP要消耗内核资源(内核线程的栈空间)。因此一个系统不能支持大量的LWP。

LWP.JPG 

注:

LWP 的术语是借自于 SVR4/MP 和 Solaris 2.x 。有些系统将 LWP 称为虚拟处理器。而将之称为轻量级进程的原因可能是:在内核线程的支持下, LWP 是独立的调度单元,就像普通的进程一样。所以 LWP 的最大特点还是每个 LWP 都有一个内核线程支持。

 

用户线程

LWP虽然本质上属于用户线程,但LWP线程库是建立在内核之上的,LWP的许多操作都要进行系统调用,因此效率不高。而这里的用户线程指的是完全建立在用户空间的线程库,用户线程的建立,同步,销毁,调度完全在用户空间完成,不需要内核的帮助。因此这种线程的操作是极其快速的且低消耗的。

Uthread1.JPG 

上图是最初的一个用户线程模型,从中可以看出,进程中包含线程,用户线程在用户空间中实现,内核并没有直接对用户线程进程调度,内核的调度对象和传统进程一样,还是进程本身,内核并不知道用户线程的存在。用户线程之间的调度由在用户空间实现的线程库实现。

这种模型对应着恐龙书中提到的多对一线程模型,其缺点是一个用户线程如果阻塞在系统调用中,则整个进程都将会阻塞。

加强版的用户线程——用户线程+LWP

这种模型对应着恐龙书中多对多模型。用户线程库还是完全建立在用户空间中,因此用户线程的操作还是很廉价,因此可以建立任意多需要的用户线程。操作系统提供了 LWP 作为用户线程和内核线程之间的桥梁。 LWP 还是和前面提到的一样,具有内核线程支持,是内核的调度单元,并且用户线程的系统调用要通过 LWP ,因此进程中某个用户线程的阻塞不会影响整个进程的执行。用户线程库将建立的用户线程关联到 LWP 上, LWP 与用户线程的数量不一定一致。当内核调度到某个 LWP 上时,此时与该 LWP 关联的用户线程就被执行。
Uthread2.JPG 

很多文献中都认为轻量级进程就是线程,实际上这种说法并不完全正确,从前面的分析中可以看到,只有在用户线程完全由轻量级进程构成时,才可以说轻量级进程就是线程。

 

LinuxThreads

  所实现的就是基于核心轻量级进程的"一对一"线程模型,一个线程实体对应一个核心轻量级进程,而线程之间的管理在核外函数库(我们常用的pthread库)中实现。 一直以来, linux内核并没有线程的概念. 每一个执行实体都是一个task_struct结构, 通常称之为进程. 

  进程是一个执行单元, 维护着执行相关的动态资源. 同时, 它又引用着程序所需的静态资源.通过系统调用clone创建子进程时, 可以有选择性地让子进程共享父进程所引用的资源. 这样的子进程通常称为轻量级进程,如上文所述,又叫内核线程

[插曲]说下fork和vfork的区别
  fork时,子进程是父进程的一个拷贝。子进程从父进程那得到了数据段和堆栈段,但不是与父进程共享而是单独分配内存。然而这里的非共享最初状态是共享的,linux下使用了写时复制技术,刚开始共享父进程的数据段,在写数据段的时候才进行复制,以fork为例,最终共享的资源就是task_struct、系统空间堆栈(copy_thread)、页面表等。
  vfork时,因为实现为子进程先执行,所以是不拷贝(没必要)父进的虚存空间,也就是用户空间堆栈,clone(clone_vfork|clone_vm|sigchld,0),指明的参数是不会拷贝,注定共享的。因为现在的fork都是写实拷贝,所以vfork的优势便不明显了——只是不用向vfork那样拷贝页表。另外,内核都是优先让子进程先执行,考虑到调度问题,fork不保证;但vfork可保证这点  2.4内核 do_dork是fork/vfork/clone系统调用的共同代码,其核心流程如下:
  1) 默认对所有资源进行共享暂不复制;
  2) 如果flags未指定共享(相应位为0),则进行深层次复制。包括,file,fs,sighand,mm。
     以CLONE_FILES为例,对fork而言为1,也就是必须复制两份files,这样父子进程才有独立上下文(各自独立lseek不影响,但是文件指针肯定还是指向一个);但是对于vfork,这个标志为1,也就是父子进程共享文件上下文(注意这里不是共享文件指针,连上下文都共享!也就是子进程lseek会改变父进程读写位置),这岂不乱套(类似还有vfork的CLONE_VM标志)!别担心,do_fork中会保证vfork时候,自进程先执行完!
     特殊说下,mm资源即使是非共享的,即CLONE_VM=1(fork如此),也不马上复制,而是复制页面表后,把也表项设置为写保护,这样无论谁写,届时都会再复制一份出来,这才完成的资源的独立——对fork而言。
  3) 复制系统堆栈(区别于用户空间VM)

 

用户态线程由pthread库实现,使用pthread以后, 在用户看来, 每一个task_struct就对应一个线程, 而一组线程以及它们所共同引用的一组资源就是一个进程.  但是, 一组线程并不仅仅是引用同一组资源就够了, 它们还必须被视为一个整体.

POSIX线程实现基于如下要求:
1, 查看进程列表的时候, 相关的一组task_struct应当被展现为列表中的一个节点;
2, 发送给这个"进程"的信号(对应kill系统调用), 将被对应的这一组task_struct所共享, 并且被其中的任意一个"线程"处理;
3, 发送给某个"线程"的信号(对应pthread_kill), 将只被对应的一个task_struct接收, 并且由它自己来处理;
4, 当"进程"被停止或继续时(对应SIGSTOP/SIGCONT信号), 对应的这一组task_struct状态将改变;
5, 当"进程"收到一个致命信号(比如由于段错误收到SIGSEGV信号), 对应的这一组task_struct将全部退出;
6, 以上可能不全;

  在linux 2.6以前, pthread线程库对应的实现是一个名叫linuxthreads的lib. linuxthreads利用前面提到的轻量级进程来实现线程, 但是对于POSIX提出的那些要求, linuxthreads 除了第5点以外, 都没有实现(实际上是无能为力):
1, 如果运行了A程序, A程序创建了10个线程, 那么在shell下执行ps命令时将看到11个A进程, 而不是1个(注意, 也不是10个, 下面会解释);
2, 不管是kill还是pthread_kill, 信号只能被一个对应的线程所接收;
3, SIGSTOP/SIGCONT信号只对一个线程起作用;

  还好linuxthreads实现了第5点, 我认为这一点是最重要的. 如果某个线程"挂"了, 整个进程还在若无其事地运行着, 可能会出现很多的不一致状态. 进程将不是一个整体, 而线程也不能称为线程. 或许这也是为什么linuxthreads虽然与POSIX的要求差距甚远, 却能够存在, 并且还被使用了好几年的原因吧。是, linuxthreads为了实现这个"第5点", 还是付出了很多代价, 并且创造了linuxthreads本身的一大性能瓶颈.

  接下来要说说, 为什么A程序创建了10个线程, 但是ps时却会出现11个A进程了. 因为linuxthreads自动创建了一个管理线程. 上面提到的"第5点"就是靠管理线程来实现的.当程序开始运行时, 并没有管理线程存在(因为尽管程序已经链接了pthread库, 但是未必会使用多线程). 程序第一次调用pthread_create时, linuxthreads发现管理线程不存在, 于是创建这个管理线程. 这个管理线程是进程中的第一个线程(主线程)的儿子.
  然后在pthread_create中, 会通过pipe向管理线程发送一个命令, 告诉它创建线程. 即是说, 除主线程外, 所有的线程都是由管理线程来创建的, 管理线程是它们的父亲.于是, 当任何一个子线程退出时, 管理线程将收到SIGUSER1信号(这是在通过clone创建子线程时指定的). 管理线程在对应的sig_handler中会判断子线程是否正常退出, 如果不是, 则杀死所有线程, 然后自杀.
  那么, 主线程怎么办呢? 主线程是管理线程的父亲, 其退出时并不会给管理线程发信号. 于是, 在管理线程的主循环中通过getppid检查父进程的ID号, 如果ID号是1, 说明父亲已经退出, 并把自己托管给了init进程(1号进程). 这时候, 管理线程也会杀掉所有子线程, 然后自杀. 那么, 如果主线程是调用pthread_exit主动退出的呢? 按照posix的标准,这种情况下其他子线程是应该继续运行的. 于是, 在linuxthreads中, 主线程调用pthread_exit以后并不会真正退出, 而是会在pthread_exit函数中阻塞等待所有子线程都退出了, pthread_exit才会让主线程退出. (在这个等等过程中, 主线程一直处于睡眠状态.)

  可见, 线程的创建与销毁都是通过管理线程来完成的, 于是管理线程就成了linuxthreads的一个性能瓶颈. 创建与销毁需要一次进程间通信, 一次上下文切换之后才能被管理线程执行, 并且多个请求会被管理线程串行地执行.


NPTL(Native POSIX Threading Library)

到了linux 2.6, glibc中有了一种新的pthread线程库NPTL. NPTL实现了前面提到的POSIX的全部5点要求. 但是, 实际上, 与其说是NPTL实现了, 不如说是linux内核实现了.

在linux 2.6中, 内核有了线程组的概念, task_struct结构中增加了一个tgid(thread group id)字段. 如果这个task是一个"主线程", 则它的tgid等于pid, 否则tgid等于进程的pid(即主线程的pid),此外,每个线程有自己的pid。在clone系统调用中, 传递CLONE_THREAD参数就可以把新进程的tgid设置为父进程的tgid(否则新进程的tgid会设为其自身的pid).类似的XXid在task_struct中还有两个:task->signal->pgid保存进程组的打头进程的pid、task->signal->session保存会话打头进程的pid。通过这两个id来关联进程组和会话。

  有了tgid, 内核或相关的shell程序就知道某个tast_struct是代表一个进程还是代表一个线程, 也就知道在什么时候该展现它们, 什么时候不该展现(比如在ps的时候, 线程就不要展现了).而getpid(获取进程ID)系统调用返回的也是tast_struct中的tgid, 而tast_struct中的pid则由gettid系统调用来返回.在执行ps命令的时候不展现子线程,也是有一些问题的。比如程序a.out运行时,创建了一个线程。假设主线程的pid是10001、子线程是10002(它们的tgid都是10001)。这时如果你kill 10002,是可以把10001和10002这两个线程一起杀死的,尽管执行ps命令的时候根本看不到10002这个进程。如果你不知道linux线程背后的故事,肯定会觉得遇到灵异事件了。

  为了应付"发送给进程的信号"和"发送给线程的信号", task_struct里面维护了两套signal_pending, 一套是线程组共享的, 一套是线程独有的.通过kill发送的信号被放在线程组共享的signal_pending中, 可以由任意一个线程来处理; 通过pthread_kill发送的信号(pthread_kill是pthread库的接口, 对应的系统调用中tkill)被放在线程独有的signal_pending中, 只能由本线程来处理.

  当线程停止/继续, 或者是收到一个致命信号时, 内核会将处理动作施加到整个线程组中.


NGPT(Next Generation POSIX Threads)

  上面提到的两种线程库使用的都是内核级线程(每个线程都对应内核中的一个调度实体), 这种模型称为1:1模型(1个线程对应1个内核级线程);而NGPT则打算实现M:N模型(M个线程对应N个内核级线程), 也就是说若干个线程可能是在同一个执行实体上实现的. 
  线程库需要在一个内核提供的执行实体上抽象出若干个执行实体, 并实现它们之间的调度. 这样被抽象出来的执行实体称为用户级线程.大体上, 这可以通过为每个用户级线程分配一个栈, 然后通过longjmp的方式进行上下文切换. (百度一下"setjmp/longjmp", 你就知道.)
  但是实际上要处理的细节问题非常之多. 目前的NGPT好像并没有实现所有预期的功能, 并且暂时也不准备去实现.

  用户级线程的切换显然要比内核级线程的切换快一些, 前者可能只是一个简单的长跳转, 而后者则需要保存/装载寄存器, 进入然后退出内核态. (进程切换则还需要切换地址空间等).而用户级线程则不能享受多处理器, 因为多个用户级线程对应到一个内核级线程上, 一个内核级线程在同一时刻只能运行在一个处理器上.不过, M:N的线程模型毕竟提供了这样一种手段, 可以让不需要并行执行的线程运行在一个内核级线程对应的若干个用户级线程上, 可以节省它们的切换开销.

  据说一些类UNIX系统(如Solaris)已经实现了比较成熟的M:N线程模型, 其性能比起linux的线程还是有着一定的优势.



  Linux查看进程的所有子进程和线程  

得到进程的pid:
ps -ef | grep process_name | grep -v "grep" | awk '{print $2}'

查看进程的所有线程
# ps mp 6648 -o THREAD,tid
USER %CPU PRI SCNT WCHAN USER SYSTEM TID
root 0.0 - - - - - -
root 0.0 24 - - - - 6648
root 0.0 21 - - - - 6650
root 1.0 24 - - - - 14214
root 0.0 23 - futex_ - - 14216
root 0.0 22 - 184466 - - 15374
root 0.0 23 - 184466 - - 15376
root 0.0 23 - 184466 - - 15378
root 0.0 23 - 184466 - - 15380
root 0.0 23 - 184466 - - 15392
root 0.0 23 - 184466 - - 15394
root 0.0 23 - 184466 - - 15398

查看所有子进程:
# pstree -p 6648
agent_executor(6648)─┬─tar(15601)───gzip(15607)
├─{agent_executor}(6650)
├─{agent_executor}(14214)
├─{agent_executor}(14216)
├─{agent_executor}(15374)
├─{agent_executor}(15376)
├─{agent_executor}(15378)
├─{agent_executor}(15380)
├─{agent_executor}(15392)
├─{agent_executor}(15394)
└─{agent_executor}(15398)

 
$i ${a}% ${b}% ${rate}% ${space_rate}% ${e} false 

$c $g $d $imgcache_space_rate


查看/proc/pid/status可以看到一些进程的当前状态:

Name: bash
State: S (sleeping)
SleepAVG: 98%
Tgid: 11237
Pid: 11237
PPid: 11235
TracerPid: 0
Uid: 0 0 0 0
Gid: 0 0 0 0
FDSize: 256
Groups: 0 1 2 3 4 6 10
VmPeak: 66260 kB
VmSize: 66228 kB
VmLck: 0 kB
VmHWM: 1684 kB
VmRSS: 1684 kB
VmData: 456 kB
VmStk: 88 kB
VmExe: 712 kB
VmLib: 1508 kB
VmPTE: 68 kB
StaBrk: 008c3000 kB
Brk: 011b1000 kB
StaStk: 7fff8b728170 kB
Threads: 1
SigQ: 1/30222
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000010000
SigIgn: 0000000000384004
SigCgt: 000000004b813efb
CapInh: 0000000000000000
CapPrm: 00000000fffffeff
CapEff: 00000000fffffeff
Cpus_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000003
Mems_allowed: 1





LINUX中查看进程的多线程


在SMP系统中,我们的应用程序经常使用多线程的技术,那么在Linux中如何查看某个进程的多个线程呢?

本文介绍3种命令来查看Linux系统中的线程(LWP)的情况:
在我的系统中,用qemu-system-x86_64命令启动了一个SMP的Guest,所以有几个qemu的线程,以此为例来说明。

1. pstree 命令,查看进程和线程的树形结构关系。


2. ps 命令,-L参数显示进程,并尽量显示其LWP(线程ID)和NLWP(线程的个数)。

上面命令查询结果的第二列为PID,第三列为PPID,第四列为LWP,第六列为NLWP。

另外,ps命令还可以查看线程在哪个CPU上运行,命令如下:

其中,每一列依次为:用户ID,进程ID,父进程ID,线程ID,运行该线程的CPU的序号,命令行参数(包括命令本身)。

3. top 命令,其中H命令可以显示各个线程的情况。(在top命令后,按H键;或者top -H)

在top中也可以查看进程(进程)在哪个CPU上执行的。
执行top后,按f,按j(选中* J: P = Last used cpu (SMP)),然后按空格或回车退出设置,在top的显示中会多出P这一列是最近一次运行该线程(进程)的CPU.


更多信息,请 man pstree, man top, man ps 查看帮助文档。

注: LWP为轻量级进程(即:线程),(light weight process, or thread) 。

0.最常用 pstree:
[root@iZ25dcp92ckZ temp]# pstree -a|grep multe
  |       |   `-multepoolser
  |       |       `-multepoolser
  |       |           `-2*[{multepoolser}]

1. > top

可以显示所有系统进程

按u, 再输入相应的执行用户名称,比如Tom

可以看到Tom用户启动的所有进程和对应的pid

2. > pstack pid

可以看到此pid下,各线程的运行状态、

[root@test multepoolserver]# pstack  14944  (进程的PID号)
Thread 2 (Thread 0x41ed5940 (LWP 14945)):
#0  0x0000003c9ae0d5cb in read () from /lib64/libpthread.so.0
#1  0x00000000004017b6 in sync_additional_writing_worker ()
#2  0x0000003c9ae064a7 in start_thread () from /lib64/libpthread.so.0
#3  0x0000003c9a2d3c2d in clone () from /lib64/libc.so.6
Thread 1 (Thread 0x2b24b3094250 (LWP 14944)):
#0  0x0000003c9a2d4018 in epoll_wait () from /lib64/libc.so.6
#1  0x0000000000401d59 in Process ()
#2  0x00000000004029b8 in main ()

来自:http://blog.csdn.net/wind_324/article/details/6152912

方法一:
ps -ef f 
用树形显示进程和线程
在Linux下面好像因为没有真正的线程,是用进程模拟的,有一个是辅助线程,所以真正程序开的线程应该只有一个。

方法二:
[root@apache dhj]# ps axm|grep httpd

方法三:
另外用pstree -c也可以达到相同的效果,但是没有线程号:
[root@apache dhj]# pstree -c|grep httpd

来自:http://blog.chinaunix.net/uid-346158-id-2131012.html

1. pstree

pstree以树结构显示进程

  1. root@119.10.6.*:~# pstree  
  2. init─┬─NetworkManager  
  3.      ├─abrt-dump-oops  
  4.      ├─abrtd  
  5.      ├─atd  
  6.      ├─auditd───{auditd}  
  7.      ├─automount───4*[{automount}]  
  8.      ├─certmonger  
  9.      ├─crond  
  10.      ├─dbus-daemon  
  11.      ├─hald─┬─hald-runner─┬─hald-addon-acpi  
  12.      │      │             └─hald-addon-inpu  
  13.      │      └─{hald}  
  14.      ├─httpd─┬─httpd  
  15.      │       └─4*[httpd───26*[{httpd}]]  
  16.      ├─irqbalance  
  17.      ├─mcelog  
  18.      ├─6*[mingetty]  
  19.      ├─modem-manager  
  20.      ├─mysqld_safe───mysqld───38*[{mysqld}]  
  21.      ├─nginx───13*[nginx]  
  22.      ├─php-fpm───76*[php-fpm]  
  23.      ├─portreserve  
  24.      ├─rpc.idmapd  
  25.      ├─rpc.statd  
  26.      ├─rpcbind  
  27.      ├─rsyslogd───3*[{rsyslogd}]  
  28.      ├─2*[sendmail]  
  29.      ├─sshd───sshd───bash───pstree  
  30.      ├─udevd───2*[udevd]  
  31.      ├─wpa_supplicant  
  32.      └─xinetd  



2. ps -Lf
$ ps -Lf 1892
如下:PHP进程共启动了0个线程
root@119.10.6.**:~# ps -Lf 1892
UID        PID  PPID   LWP  C NLWP STIME TTY      STAT   TIME CMD
root      1892     1  1892  0    1 Jan15 ?        Ss     0:53 php-fpm: master process (/usr/local/php/etc/php-fpm.conf)

Mysql多个线程:
root@119.10.6.*:~#  ps -Lf 3005
UID        PID  PPID   LWP  C NLWP STIME TTY      STAT   TIME CMD
mysql     3005  2011  3005  0   39 Jan15 ?        Sl     2:25 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=
mysql     3005  2011  3081  0   39 Jan15 ?        Sl     0:00 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=
mysql     3005  2011  3082  0   39 Jan15 ?        Sl     0:05 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=
mysql     3005  2011  3083  0   39 Jan15 ?        Sl     0:00 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=
mysql     3005  2011  3084  0   39 Jan15 ?        Sl     0:00 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=
mysql     3005  2011  3085  0   39 Jan15 ?        Sl     0:00 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=
mysql     3005  2011  3086  0   39 Jan15 ?        Sl     0:00 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=
mysql     3005  2011  3087  0   39 Jan15 ?        Sl     0:05 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=

3. pstack
pstack显示每个进程的栈跟踪,PHP的fpm主进程,发现这玩意是走的epoll:
root@119.10.6.23:~# pstack 1892
#0  0x00000030e9ae8fb3 in __epoll_wait_nocancel () from /lib64/libc.so.6
#1  0x0000000000856a74 in fpm_event_epoll_wait ()
#2  0x000000000084afff in fpm_event_loop ()
#3  0x0000000000845ee7 in fpm_run ()
#4  0x000000000084d900 in main ()
You have new mail in /var/spool/mail/root


How to view threads of a process on Linux

Question:  My program creates and executes multiple threads in it. How can I monitor individual threads of the program once they are created? I would like to see the details (e.g., CPU/memory usage) of individual threads with their names.

Threads are a popular programming abstraction for parallel execution on modern operating systems. When threads are forked inside a program for multiple flows of execution, these threads share certain resources (e.g., memory address space, open files) among themselves to minimize forking overhead and avoid expensive IPC (inter-process communication) channel. These properties make threads an efficient mechanism for concurrent execution.

In Linux, threads (also called Lightweight Processes (LWP)) created within a program will have the same "thread group ID" as the program's PID. Each thread will then have its own thread ID (TID). To the Linux kernel's scheduler, threads are nothing more than standard processes which happen to share certain resources. Classic command-line tools such as psor top, which display process-level information by default, can be instructed to display thread-level information.

Here are several ways to show threads for a process on Linux.

Method One: PS

In ps command, "-T" option enables thread views. The following command list all threads created by a process with .

$ ps -T -p

The "SID" column represents thread IDs, and "CMD" column shows thread names.

Method Two: Top

The top command can show a real-time view of individual threads. To enable thread views in the top output, invoke topwith "-H" option. This will list all Linux threads. You can also toggle on or off thread view mode while top is running, by pressing 'H' key.

$ top -H

To restrict the top output to a particular process and check all threads running inside the process:

$ top -H -p

Method Three: Htop

A more user-friendly way to view threads per process is via htop, an ncurses-based interactive process viewer. This program allows you to monitor individual threads in tree views.

To enable thread views in htop, launch htop, and press to enter htop setup menu. Choose "Display option" under "Setup" column, and toggle on "Three view" and "Show custom thread names" options. Presss to exit the setup.

Now you will see the follow threaded view of individual processes.

Download this article as ad-free PDF (made possible by your kind donation): 
Download PDF







About Me

.............................................................................................................................................

● 本文整理自网络

● 本文在itpub(http://blog.itpub.net/26736162/abstract/1/)、博客园(http://www.cnblogs.com/lhrbest)和个人微信公众号(xiaomaimiaolhr)上有同步更新

● 本文itpub地址:http://blog.itpub.net/26736162/abstract/1/

● 本文博客园地址:http://www.cnblogs.com/lhrbest

● 本文pdf版、个人简介及小麦苗云盘地址:http://blog.itpub.net/26736162/viewspace-1624453/

● 数据库笔试面试题库及解答:http://blog.itpub.net/26736162/viewspace-2134706/

● DBA宝典今日头条号地址:http://www.toutiao.com/c/user/6401772890/#mid=1564638659405826

.............................................................................................................................................

● QQ群号:230161599(满)、618766405

● 微信群:可加我微信,我拉大家进群,非诚勿扰

● 联系我请加QQ好友646634621,注明添加缘由

● 于 2017-08-01 09:00 ~ 2017-08-31 22:00 在魔都完成

● 文章内容来源于小麦苗的学习笔记,部分整理自网络,若有侵权或不当之处还请谅解

● 版权所有,欢迎分享本文,转载请保留出处

.............................................................................................................................................

小麦苗的微店https://weidian.com/s/793741433?wfr=c&ifr=shopdetail

小麦苗出版的数据库类丛书http://blog.itpub.net/26736162/viewspace-2142121/

.............................................................................................................................................

使用微信客户端扫描下面的二维码来关注小麦苗的微信公众号(xiaomaimiaolhr)及QQ群(DBA宝典),学习最实用的数据库技术。

   小麦苗的微信公众号      小麦苗的DBA宝典QQ群1     小麦苗的DBA宝典QQ群2        小麦苗的微店

.............................................................................................................................................

img_e3029f287d989cd04bd75432ecc1c172.png
DBA笔试面试讲解群1
DBA笔试面试讲解群2
欢迎与我联系



相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
2天前
|
Java 数据库连接 数据处理
Python从入门到精通:3.1.2多线程与多进程编程
Python从入门到精通:3.1.2多线程与多进程编程
|
3天前
|
NoSQL Linux 程序员
【linux进程信号(一)】信号的概念以及产生信号的方式
【linux进程信号(一)】信号的概念以及产生信号的方式
|
3天前
|
Linux
【linux进程间通信(一)】匿名管道和命名管道
【linux进程间通信(一)】匿名管道和命名管道
|
3天前
|
Java Shell Linux
【linux进程控制(三)】进程程序替换--如何自己实现一个bash解释器?
【linux进程控制(三)】进程程序替换--如何自己实现一个bash解释器?
|
3天前
|
算法 Linux Shell
【linux进程(二)】如何创建子进程?--fork函数深度剖析
【linux进程(二)】如何创建子进程?--fork函数深度剖析
|
3天前
|
存储 Linux Shell
【linux进程(一)】深入理解进程概念--什么是进程?PCB的底层是什么?
【linux进程(一)】深入理解进程概念--什么是进程?PCB的底层是什么?
|
3天前
|
固态存储 Ubuntu Linux
Linux(29) 多线程快速解压缩|删除|监视大型文件
Linux(29) 多线程快速解压缩|删除|监视大型文件
11 1
|
4天前
|
消息中间件 Unix Linux
Linux的学习之路:17、进程间通信(1)
Linux的学习之路:17、进程间通信(1)
19 1
|
4天前
|
存储 安全 Linux
Linux的学习之路:9、冯诺依曼与进程(1)
Linux的学习之路:9、冯诺依曼与进程(1)
18 0
|
9天前
|
调度 Python
Python多线程、多进程与协程面试题解析
【4月更文挑战第14天】Python并发编程涉及多线程、多进程和协程。面试中,对这些概念的理解和应用是评估候选人的重要标准。本文介绍了它们的基础知识、常见问题和应对策略。多线程在同一进程中并发执行,多进程通过进程间通信实现并发,协程则使用`asyncio`进行轻量级线程控制。面试常遇到的问题包括并发并行混淆、GIL影响多线程性能、进程间通信不当和协程异步IO理解不清。要掌握并发模型,需明确其适用场景,理解GIL、进程间通信和协程调度机制。
28 0