线程概念
- 一.线程
- 1.简单理解
- 2.一些疑问
- 3.简单说一下优缺点,异常和用途
- 二.进程VS线程
- 1.进程和线程的联系和区别
- 2.phread线程库(创建)
- 3.线程的LWP
- 4.线程等待
- 5.线程终止
- 三.C++11里的线程
- 四.创建线程最底层接口
一般教程里定义线程:是进程内的一个执行分支。线程的执行粒度要比进程细。实际上这更是线程的一个特征而不是它的根本。
一.线程
1.简单理解
前面我们说过每一个进程都具有独立性,它们有自己独立的PCB,地址空间,页表…如果我们创建一个"进程",只创建它的PCB,让它指向上一个进程的地址空间呢?像这种能与上一个进程共享地址空间的“进程”被称为线程。
2.一些疑问
疑问一
线程为什么要在进程的地址空间里运行?
因为任何执行流要执行,都需要有资源,而地址空间就是资源的窗口。如果它在独立的地址空间里运行,那么它就是新的进程。如果它在其他进程的地址空间里运行,那么它就是线程。这是人为的定义。
疑问二
线程为什么执行粒度比进程更细?
显而易见,线程的资源与其他执行流共享,所以当然更细。
疑问三
原来我们理解进程是进程=内核数据结构(task_struct)+代码和数据,那么这里出现了多个PCB又该怎么理解呢?
重新理解进程:进程是承担分配系统资源的基本实体。也就是说上图里,一大堆执行流,地址空间,页表,物理内存分配的空间…统称为进程。
疑问四
如何将以前的概念和现在的概念联系起来呢?
以前创建进程,我们为它分配地址空间,页表,内存…资源,但只有一个执行流。现在依然为它分配资源,但有多个执行流,本质没有区别。如果要区分,就是原来的情况是进程的特殊情况,现在是一般情况。
疑问五
操作系统需要对线程进行管理吗?
答案是肯定的,先描述后组织。在大部分操作系统里确实有一个struct_tcb来描述线程,但在Linux里为了简便,直接使用进程来模拟线程。
疑问六
线程如何进行资源分配?
线程目前进行资源分配的本质就是进行地址空间的分配。
疑问七
常说线程比进程更轻量化,体现在哪呢?
1.创建和释放更轻量化。(不需要额外创建地址空间…)
2.切换更轻量化。(不需要重写catch里的数据)
3.简单说一下优缺点,异常和用途
优点
1.创建一个新线程的代价要比创建一个新进程小得多 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多 线程占用的资源要比进程少很多
2.能充分利用多处理器的可并行数量 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
3.计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
4.I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
缺点
1.性能损失: 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型 线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的 同步和调度开销,而可用的资源不变。
2.健壮性降低: 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了 不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
3.缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
4.编程难度提高:编写与调试一个多线程程序比单线程程序困难得多。
异常
1.单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃。
2.线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出。
用途
1.合理的使用多线程,能提高CPU密集型程序的执行效率。
2.合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)。
二.进程VS线程
1.进程和线程的联系和区别
一些概念
1进程是资源分配的基本单位。
2.线程是调度的基本单位。
3.线程共享进程数据,但也拥有自己的一部分数据。
线程独有的
1.线程ID
2.一组寄存(重要)
3.栈(重要)
4.errno
5.信号屏蔽字
6.调度优先级
7.........
共享
进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
1文件描述符表
2.每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
3.当前工作目录
4.用户id和组id
2.phread线程库(创建)
因为Linux是用进程模拟线程,所以Linux里并没有很明确线程的概念。它被称为轻量级进程。也就是说Linux不会提供线程的系统调用,而是提供轻量级进程的系统调用!
但是我们需要线程的接口。所以程序员创建了一个pthread线程库,在应用层对轻量级进程接口进行封装。为我们用户提供直接线程的接口。
简单认识
第一个参数:pthread_t是一个输出型参数。它会把线程id带出。
第二个参数:是线程属性。一般不用管设置成NULL即可。
第三个参数:是函数指针。指向你需要的执行的函数,当线程执行时,它就会从该指针指向的函数的地址开始执行。
第四个参数:创建线程成功,新线程回调线程函数时,如果需要参数,这个参数就传给线程函数。不需要就设在成NULL。
返回值:如果成功就返回0,否则就返回错误码。
简单使用
由于我们需要使用第三方库,所以在编译时需要手动连接。
可以看到两个线程的pid是一样的,所以说明它们是同一个进程里的两个执行流。
3.线程的LWP
查看轻量级进程个数
PID毫无疑问是进程标识符,而LWP就是轻量级进程标识符。再仔细观察,有一个线程的LWP就等于进程PID,这个线程就叫做主线程。
我们给任意一个线程发送信号,其他线程都会收到信号,所以我们认为信号是发送给进程的。
获取线程ID
前面说过函数的第一个参数是输出型参数,就是用来获取线程ID。
pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线ID,
属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
我们也可以使用函数在该线程内部获取它的ID
4.线程等待
第一个参数很明显是线程的id。
第二个参数是一个二级指针,它指向的其实就是该线程函数的返回值。因为线程函数执行完毕后要求返回void*类型指针,所以接收就需要使用二级指针。
5.线程终止
前面我们学过进程终止可以使用exit,那么线程可不可以呢?很明显是不可以的,因为exit不仅退出了当前线程,更退出了该进程里的所有线程。退出线程有专门的函数。
这里参数的含义和上面进程等待retval的含义一样,都是带出值。
三.C++11里的线程
在C++11里,也支持多线程,实际上就是对系统调用接口进行了一系列的封装。
四.创建线程最底层接口
上面说过Linux是用进程模拟的线程,所以没有直接的线程接口。我们使用第三方库来进行封装后,让我们调用简便。那么Linux最底层的接口是什么呢?
clone接口就是专门用来创建轻量级进程的,可以看出它很麻烦,我们一般不使用。
第一个参数:函数指针,线程进入的函数。
第二个参数:自定义栈。
第三个参数:是否实现地址空间共享。
其他参数................
第三方库的封装