您的位置:首页 > 运维架构 > Linux

Linux中线程的概念及创建应用

2017-06-07 20:31 281 查看
一.线程的概念

线程,是程序执行流的最小单元。线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机 ; 运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。

每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。

之前没有学过线程的概念之前,所谓的pid指的是进程的id,一个进程有一个对应的PCB。但是引入了线程的概念之后,一个进程实际上对应的是一组PCB,因为线程的描述也是由PCB来描述的,这样的一个进程内的一组线程叫做线程组。

对于系统调用getpid(),它返回的其实并不是这个进程的pid,而是这个线程组的组id(tgid),即“Thread Group ID“”。该线程组的组id就是该线程组的主线程的id(主函数main)。想要拿到具体某个轻量级进程(线程)的id,可以用系统调用gettid()。

实际上,线程有两个ID,一个是内核给每个线程的LWP(ID);另一个则是POSIX中的线程库的线程ID,也就是pthread_self()返回的线程ID,这个系统调用实际上返回的是进程地址空间上的一个地址。

二.线程与进程的区别

在Linux下并没有专门为线程设计这么一个概念,也就是说没有真正意义上的线程,它在Linux下是由进程模拟的,可以认为所有的PCB都可以称为轻量级进程(不一定是进程,也可能是线程),简称LWP。进程是分配系统资源的一个实体,而线程是CPU调度的基本单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。相对来说,多进程相对稳定,多线程相对不稳定。

进程具有独立的地址空间,而线程并没有,同一进程内部的线程共享进程的地址空间。

三.多线程相对于多进程的优缺点

1.多线程的优点:

无需跨进程边界;

程序逻辑和控制方式简单;

所有线程可以直接共享内存和变量等;

线程方式消耗的总资源比进程方式好;

2.多线程的缺点:

每个线程与主程序共用地址空间,受限于有限的地址空间;

线程之间的同步和加锁控制比较麻烦;

一个线程的崩溃可能影响到整个程序的稳定性;

到达一定的线程数程度后,即使再增加CPU也无法提高性能;

线程能够提高的总性能有限,而且线程多了之后,线程本身的调度也很麻烦,需要消耗较多的CPU;

3.多进程的优点:

每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系;

通过增加CPU,就可以容易扩充性能;

可以尽量减少线程加锁/解锁的影响,极大提高性能,就算是线程运行的模块算法效率低也没关系;

每个子进程都有2GB地址空间和相关资源,总体能够达到的性能上限非常大;

4.多进程的缺点:

逻辑控制复杂,需要和主程序交互;

需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算;

多进程调度开销比较大;

四.线程所具有的资源

线程共享的进程资源及环境:

1. 文件描述符表

2. 虚拟地址空间(实际上是页表,并不包括调用栈)

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

4. 当前工作目录

5. 用户id和组id

每个线程私有一份的资源:

1. 栈空间

2. 上下文信息,包括各种寄存器的值、程序计数器和栈指针

3. 调度优先级

4. errno变量

5. 信号屏蔽字

6. 线程id

五.线程的创建/等待/终止

1.进程的创建函数pthread_create



参数声明:

thread:返回线程ID

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

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

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

此函数成功返回0,失败返回错误号,实际上pthread中的函数失败的返回值都是返回错误号

关于错误信息的检查

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

- pthreads函数错误时不会设置全局变量errno(而大部分其他POSIX函数会这样做),而是将错误代码通过返回值返回。

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

2.进程的终止函数pthread_exit

实际上,线程的终止有三种方式:

①return可以表示线程终止

②使用pthread_exit表示线程终止

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



参数声明:

retval:retval不要指向一个局部变量

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

3.进程的取消函数pthread_cancel



thread即为另一线程的线程ID

4.进程的等待函数pthread_join

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

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



参数声明:

thread:线程ID

value_ptr:它指向一个指针,该指针指向线程的返回值

同样的,成功返回0,失败返回错误号。

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过

pthread_join得到的终止状态是不同的,总结如下:

[b]如果thread线程通过return返回,value_ ptr所指向的单元⾥里存放的是thread线程函数的返回值。


如果thread线程被别的线程调⽤用pthread_ cancel异常终止掉,value_ptr所指向的单元里存放的是常数PTHREAD_ CANCELED(通过验证可以得到该常数是-1)。

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

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

5.线程的分离函数pthread_detach

默认情况下,新创建的线程时joinable的,线程退出后,需要调用pthread_jion函数来等待,否则无法释放线程资源,导致系统内存泄漏。

当我们不关心线程的返回值时,jion显然是多余的,这个时候我们可以调用pthread_detach函数来分离线程,告诉系统线程退出时,自动释放线程资源。

int pthread_detach(pthread_t thread);


可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离。

pthread_detach(pthread_self());


joinable和分离是冲突的,一个线程不可能既是joinable的又是分离的。

相关的测试代码如下:



在这里,thread_run函数是线程去执行的函数,而main函数是主线程所执行的函数,下面我们来看线程的三种终止方式的退出结果。

如上图所示,是第一种线程终止的方式,利用pthread_cancel函数取消线程,其测试结果如图:



可以看到其返回值ret是-1。

下面看第二种线程终止的方式,利用pthread_exit函数终止线程,其测试代码及测试结果如图:





可以看到程序最终接受到的返回码就是123,是pthread_exit中的参数值。

最后一种线程终止的方式,就是在函数中直接return返回,其测试代码及测试结果如图:





可以看到程序最终返回的就是1,就是thread_run函数中return给主函数的值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux