Linux_线程相关

线程概念

什么是线程

    在一个程序里的一个执行路线就叫做线程。更确切的来说,“线程是一个进程内部的控制序列”

    一切进程至少都有一个执行线程

进程和线程

    进程是资源竞争的基本单位

    线程是程序执行的最小单位

    线程共享进程数据,但也拥有自己的一部分数据,例如线程ID,某一组寄存器,线程的函数调用栈,信号屏蔽字,调度优先级等等

一进程的多个线程共享

    一个进程内可以有多个线程,多个线程同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程内都可以调用,如果定义一个全局变量,在各线程内都可以访问到,除此之外,各线程还共享以下进程资源和环境:

    1.文件描述符

    2.每种信号的处理方式(SIG_IGN,SIG_DFL或者自定义的信号处理函数)

    3.当前工作目录

    4.用户id和组id

进程和线程的关系如图:


线程的优点

    创建一个新线程的代价要比创建一个新进程小得多

    与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多

    线程占用的资源要比进程少得多

    能充分利用多处理器的可并行数量   

    在等待慢速I/O操作结束的同时,程序可以执行其他的计算任务

    计算密集型应用,为了能在多处理机系统上运行,将计算分解到多个线程中实现

    I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作

线程的缺点

    性能损失

        一个很少被外部事件阻塞的计算性密集线程往往无法与其他线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。

    健壮性降低

        编写多线程需要更全面更深入的考虑,在一个多线程里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是非常大的,换句话说线程之间是缺乏保护的

    缺乏访问控制

        进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响

    编程难度提高

        编写与调试一个多线程程序比单线程程序困难的多

线程控制

POSIX线程库

    与线程有关的函数构成了一个完整的系列,绝大多数的函数名字都是以"pthread_"打头的

    要使用这些函数库,要通过引入头文件

    链接这些线程函数库时要使用编译器命令的"-lpthread"选项

创建线程

功能:创建一个新的线程
原型    
    int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void*),void *arg);

参数

    thread:返回线程ID

    attr:设置线程的属性,attr为NULL表示使用默认属性

    start_routine:是个函数地址,线程启动后要执行的函数

    arg:传给线程启动函数的参数

返回值:成功返回0,失败返回错误码

错误检查:

    传统的一些函数是,成功0,失败-1,并且对全局变量errno赋值以表示错误

    pthread函数出错时不会设置全局变量errno(而大部分其他的POSIX函数会这样)。而是将错误通过返回值返回

    pthread函数同样也提供了线程内的errno变量,以支持其他使用errno的代码。对于pthread函数的错误,建议通过返回值判定,因为读取返回值要比读取线程内的errno变量的开销更小

进程ID和线程ID

    在linux中,目前的线程是实现Native POSIX Thread Libaray,简称NPTL。在这种实现下,线程又被称为轻量级进程(Light Weighted Process),每一个用户态的线程,在内核中都对应一个调度实体,也拥有自己的进程描述符(task_struct结构体)

    没有线程之前,一个进程对应内核里的一个进程描述符,对应一个进程ID,但是引入线程概念之后,情况又发生了变化,一个用户进程下管辖N个用户态线程,每个线程作为一个独立的调度实体在内核态都有自己的进程描述符。

    Linux内核引入了线程组的概念

struct task_struct{
    ...
    pid_t pid;
    pid_t tgid;
    ...
    struct task_struct *group_leader;
    ...
    struct list_head thread_group;
    ....
};

    多线程的进程,又被称为线程组,线程组内的每一个线程在内核之中都存在一个进程描述符(task_struct)与之对应。进程描述符结构体中的pid,表面上看对应的是进程ID,其实不然,它对应的是线程ID,进程描述符中的tgid,含义是Thread Group ID,该值对应的是用户层面的进程ID


查看线程ID:ps -eLf 

    -L选项:LWP:线程ID,即gettid()调用后的返回值

                NLWP:线程组内线程的个数

注意:线程和进程不一样,进程有父子进程的概念,但是在线程组里,所有的县城都是对等关系


线程ID及进程地址空间布局

    pthread_create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面提到的线程ID并不是一回事

    前面所提到的线程ID属于进程调度的范畴。因为进程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。

    pthread_create函数产生并标记在第一个参数指向的地址中的线程ID中,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程来操作线程的

    线程库NPTL提供了pthread_self函数,可以获得线程自身的ID:


线程终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

    1.从线程函数 return。这种方法对主线程不适用,从main函数return相当于调用exit。

    2.线程可以调用pthread_exit终止自己

   3.一个线程可以调用pthread_cancel终止同一进程的另一个线程

pthread_exit函数   

功能:线程终止
原型
    void pthread_exit(void* value_ptr)

参数:value_ptr不要指向一个局部变量

返回值:无返回值,跟进程一样,线程结束的时候无法返回到他的调用者(自身)

需要注意的是,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程的函数栈上分配,因为当其他线程得到这个返回值指针时线程函数已经退出了。

pthread_cancel函数

功能:取消一个执行中的线程
原型
    int pthread_cancel(pthread_t thread);

参数 thread:线程ID

返回值:成功返回0;失败返回错误码

线程等待与分离

线程等待的原因:

    已经退出的线程,其空间没有被释放,仍然在进程的地址空间内

    创建新的线程不会复用刚才退出线程的地址空间

功能:等待线程结束
原型:
    int pthread_join(pthread_t thread,void **value_ptr);

参数:thread:线程ID,value_ptr:指向一个指针,后者指向线程的返回值

返回值:成功返回0;失败返回错误码

调用该函数的线程将挂起等待,知道id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的:

    1.如果pthread线程通过return返回,value所指向的单元里存放的是thread线程函数的返回值。

    2.如果thread线程被别的线程调用pthread_canel异常终止,value_ptr里存放的是常数PTHREAD_CANCELED。

    3.如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。

    4.如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数。

分离线程

    默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄露。

    如果不关心线程的返回值,join是一种负担,这个时候可以让操作系统自动释放资源。

int pthread_detach(pthread_t thread);

可以是线程内其他函数对目标线程进行分离,也可以是自己分离;

pthread_detach(pthread_self());
joinable和分离是冲突的,一个线程不能既是joinable的又是分离的。

猜你喜欢

转载自blog.csdn.net/qq_40425540/article/details/80092240