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
![](https://img-blog.csdn.net/20180308161840360?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbHZ5aWJpbjg5MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
参数声明:
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终止同一进程的另一个进程
![](https://img-blog.csdn.net/20180308162804765?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbHZ5aWJpbjg5MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
参数声明:
retval:retval不要指向一个局部变量
需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
3.进程的取消函数pthread_cancel
![](https://img-blog.csdn.net/20180308163326492?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbHZ5aWJpbjg5MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
thread即为另一线程的线程ID
4.进程的等待函数pthread_join
已经退出的线程,其空间没有被释放,仍然在进程的地址空间内
创建新的进程不会服用刚才退出线程的地址空间
![](https://img-blog.csdn.net/20180308162105191?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbHZ5aWJpbjg5MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
参数声明:
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函数来分离线程,告诉系统线程退出时,自动释放线程资源。
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离。
joinable和分离是冲突的,一个线程不可能既是joinable的又是分离的。
相关的测试代码如下:
![](https://img-blog.csdn.net/20170608201434170?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbHZ5aWJpbjg5MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
在这里,thread_run函数是线程去执行的函数,而main函数是主线程所执行的函数,下面我们来看线程的三种终止方式的退出结果。
如上图所示,是第一种线程终止的方式,利用pthread_cancel函数取消线程,其测试结果如图:
![](https://img-blog.csdn.net/20170608201824359?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbHZ5aWJpbjg5MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
可以看到其返回值ret是-1。
下面看第二种线程终止的方式,利用pthread_exit函数终止线程,其测试代码及测试结果如图:
![](https://img-blog.csdn.net/20170608202241080?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbHZ5aWJpbjg5MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
![](https://img-blog.csdn.net/20170608202256090?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbHZ5aWJpbjg5MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
可以看到程序最终接受到的返回码就是123,是pthread_exit中的参数值。
最后一种线程终止的方式,就是在函数中直接return返回,其测试代码及测试结果如图:
![](https://img-blog.csdn.net/20170608202552800?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbHZ5aWJpbjg5MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
![](https://img-blog.csdn.net/20170608202603624?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbHZ5aWJpbjg5MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
可以看到程序最终返回的就是1,就是thread_run函数中return给主函数的值。
线程,是程序执行流的最小单元。线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机 ; 运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。
每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
之前没有学过线程的概念之前,所谓的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高级程序设计》杨宗德著 - Linux多线程编程 - 线程概念及创建线程
- Linux线程编程 - 线程概念及创建线程
- 【原创】《Linux高级程序设计》杨宗德著 - Linux多线程编程 - 线程概念及创建线程 分类: Linux --- 应用程序设计 2014-11-19 17:31 82人阅读 评论(0) 收藏
- Vs2013在Linux开发中的应用(21): 线程创建
- 【Linux】线程(概念、特点、线程控制代码--创建/等待/终止、分离与结合属性)
- linux多线程学习(二)—线程的创建和退出
- Linux下线程的创建
- Linux线程技术:概念与技术发展
- Linux线程技术的概念与技术发展
- 多线程学习-线程基本概念及线程创建--发布日期:2008-07-16 23:19
- 线程学习笔记【1】----进程、线程概念及创建线程
- 转载 linux 2.6线程创建源码分析
- Linux下线程的创建
- Unix/Linux创建一个线程
- Linux下创建进程线程以及通信技术的使用
- day10 反射创建数组 线程的基本概念 线程的编写和启动方式 线程的运行状态以及状态转换方法 线程的调度和优先级设置
- Linux 创建多进程 & 线程时的进程栈段处理
- Java 线程的基本概念 创建方法 和 基本操作
- Linux线程(1): 线程的创建
- 了解Linux线程技术的概念与技术发展