多线程编程(一):创建、等待、分离与结合

2017-05-29 23:53 阅读 301 次 评论 2 条

Linux kernel中是不存在线程的,或者说没有真正意识上的线程,Linux下的线程是用进程来模拟的,线程的实现就是多个共享数据信息的进程,我们称之为轻量级进程

多线程的引入

多线程是指操作系统在单个进程内支持多个并发执行路径的能力,线程属于进程的多个执行流。linux多线程设计包括多任务程序的设计,并发程序设计,网络程序设计,数据共享等。

Linux系统下的多线程遵循POSIX线程接口,称为POSIX thread或者pthread。在linux下线程函数位于libpthread共享库中,需要加上头文件pthread.h,连接时需要使用 libpthread.a。

Linux下pthread的实现是通过系统调用clone()来实现的,clone是linux所特有的系统调用,它的使用方式类似于fork

线程的共享与私有

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

但是,有些资源线程是各自私有一份的:

线程由内核自动调度,并且内核通过一个整数ID来识别线程。同基于I/O多路复用一样,多个线程运行在单一进程的上下文中,因此共享这个进程虚拟地址空间的整个内容,包括它的代码、数据、堆、共享库和打开的文件。

线程执行模型

多线程的执行模型在某些方面和多进程是相似的。如下图所示:

每个进程开始生命周期都是单一线程,整个线程称之为主线程(main thread)。在某一时刻,主线程创建一个对等线程(peer thread),从这个时间点开始,两个线程就并发地执行。最后,因为主线程执行一个慢速系统调用(read或sleep)等,控制就会通过上下文切换传递对等线程。对等线程会执行一段时间,然后控制传递回主线程。

线程执行时不同于进程的。因为一个线程的上下文要比一个进程的上下文小得多,线程的上下文切换要比进程的上下文切换快得多。另一个方面,线程不是按照严格的父子层次来组织的,和一个进程相关的线程组成一个对等(线程)池(pool),独立于其他线程创建的线程。

主线程和其他线程的区别仅在于它总是进程中第一个执行的线程,对等(线程)池概念的主要影响是:一个线程可以杀死他的任何对等线程,或者等待他的任意对等线程终止。另外,每个对等线程都能读写相同的共享数据。

创建线程

线程通过调研pthread_create函数来创建其他线程,它的声明如下:

参数①:输出型参数,它包含新创建线程的ID。新线程可以通过调研pthread_self函数来获得它自己的线程ID但仅在进程内部有效。

参数②:线程的属性(通常设置为NULL)。

参数③:函数指针指向所要创建的线程,即创建的线程将要跑的函数。

参数④:属于输入型参数,传递给该函数的指针。

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

下面我们创建一个线程来验证一下:

运行结果如下:

从执行结果可以看出,主线程与新创建线程的pid都是6509,即主线程与从线程从属于同一个进程,也进一步说明,在Linux下,没有真正意义上的线程。

需要注意的是pthread_self所获取到的是相对于进程的线程控制块的首地址,只是用来描述统一进程当中的不同的线程。而真正的内核当中的线程ID,对于多线程进程来说,每个tid实际是不一样的。

等待线程

在之前进程中,子进程结束时,需要父进程去回收资源,以及后续的处理,即确保子进程先于父进程退出,否则会出现僵尸进程,造成内存泄漏等问题,进程可以调用wait和waitpid函数的防止僵尸进程的出现,那么我们的线程亦如此。

线程与进程有异曲同工之妙,或者可以直接认为它就是进程,同样,新线程的释放要先于主线程,因此,我们引入了pthread_join函数。

参数①:要等待的目标线程的id。

参数②:要等待的目标线程的退出码。线程在运行时,只能运行完且返回一个退出码,如果一个线程出错,则整个进程挂掉(原因在于进程是操作系统执行的基本单位)。对void*也不能解引用,因为解引用之后未void,无法知道其大小,因此要使用void**的二级指针。

当然,线程等待属于阻塞式等待,即新线程不退出,主线程就一直等待,主线程退出,则进程退出。

和Unix的wait函数不同,pthread_join函数只能等待一个指定的线程终止,没有办法让pthread_join等待任意一个线程终止。

值得注意的是,线程可以被等待,也可以使用中止来结束新线程,下面使用程序模拟一下Linux下三种线程中止方式:

① 使用return (void*),如果在主线程处使用就等同与exit。

② 使用pthread_exit(void* retval),等同于从线程退出。

调用exit代表的是进程的退出,即该进程内的所有线程将全部退出,因此尽量不要调用exit。

注意:pthread_exit或return返回的指针指向的内存单元必须是全局的或者用malloc分配而来,不能再线程函数的栈上分配,因为当线程得到整个返回指针时,线程已经退出。

③使用 pthread_cancel:取消这个线程,即线程允许被取消,其退出码为-1。

分离与结合线程

在任何一个时间点上,线程是可结合的(joinable)或者是分离的(detached)。一个可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,它的存储器资源(例如栈)是没有被释放的。相反,一个分离的线程是不能被其他线程回收或杀死的。它的存储器资源在它终止时由系统自动释放。

默认情况下,线程被创建为可结合的。为了避免存储器泄漏,每个可结合线程都应该要么被其他线程显示收回,要么通过调用pthread_detach函数被分离。

pthread_detach总是返回0。分离分为新线程的分离和主线程的分离,下面就两者的分离作以阐述:

① 新线程的分离:新线程分离后,主线程可能不知道新线程的分离,因此主线程可能一直会去join等待。

等待失败在所难免,新线程的分离对于主线程来说是看不到的,主线程会一直去等待新线程,殊不知新线程已经被操作系统释放。

② 主线程的分离:此时主线程已经确认与新线程分离,将不再等待新线程。

主线程已经知道自己与新线程分离,因此将不再回收新线程,而是主线程执行完就直接退出。

线程总结

① 线程是在进程内部运行,实际上是在进程的地址空间上运行。

② 线程是进程的一个分支,创建线程的成本较低。

③ 线程是操作系统调度的基本单位,进程是系统资源分配的基本单位。

④ 线程属于轻量级的进程(至少在Unix和Linux上是如此)。

版权声明:本文著作权归原作者所有,欢迎分享本文,谢谢支持!
转载请注明:多线程编程(一):创建、等待、分离与结合 | 术与道的分享
分类:操作系统 标签:, ,
1024do.com导航_术与道导航平台

发表评论


表情

  1. 古巨鸡
    古巨鸡 【农民】 @回复

    ”主线程已经确认与新线程分离,将不再等待新线程“请问博主,主线程通过什么机制知道不需要再等待新线程了,可否有所依据,还是博主大胆推断?

    • w3nY@ng
      w3nY@ng 【农民】【站长】 @回复

      主线程分离后,操作系统会接受子线程,也就是说,子线程的后续处理都由操作系统来释放。