初识多线程

文章目录

  • 前言
  • 1、线程的基本概念
  • 2、线程的特点
  • 3、线程的优点
  • 4、线程的缺点
  • 5、线程异常
  • 6、线程用途
  • 7、线程与进程的比较
  • 8、线程控制
    • 8.1 POSIX线程库
    • 8.2 创建线程
    • 8.3 代码演示
  • 9、LWP概念
    • 9.1 PID与进程LWP
    • 9.2 查看LWP
    • 9.3 线程ID


前言

60年代,在OS中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端,一是由于进程是资源拥有者,创建、撤消与切换存在较大的时空开销,因此需要引入轻型进程;二是由于对称多处理机(SMP)出现,可以满足多个运行单位,而多个进程并行开销过大。
因此在80年代,出现了能独立运行的基本单位--线程(Threads)


1、线程的基本概念

  1. 线程指的是进程中一个单一顺序的控制流(执行流),属于进程的一部分
  2. 一个进程中可以并发多个线程,每条线程并行执行不同的任务(进程:线程 = 1:n)
  3. 线程是独立调度和分派的基本单位,而进程是分配资源的基本单位
  4. 同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的pcb,调用栈,自己的寄存器环境(上下文),自己的线程本地存储

重点:线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,比进程更细更轻量化。线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源(自己的调用栈,寄存器环境等),但线程可以共享进程所拥有的全部资源,同时线程也拥有各自私有资源(不能被其他线程共享)
初识多线程_第1张图片

2、线程的特点

在多线程OS中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性:
1. 轻型实体:
线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。
线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。TCB包括以下信息:

  • 线程状态
  • 当线程不运行时,被保存的现场资源
  • 一组执行堆栈
  • 存放每个线程的局部变量主存区
  • 访问同一个进程中的主存和其它资源

用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈
2. 独立调度和分派的基本单位:
在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很"轻",故线程的切换非常迅速且开销小(在同一进程中的)

3. 可并发执行:
在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力

4.共享进程资源:
在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核

3、线程的优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作

线程虽然有如此多的优点,但也不是越多越好,因为线程太多,会导致线程被过度地调度切换,调度切换浪费的时间可能就会大于提高的效率。不急如此,线程间用了共同的虚拟地址空间,就是运用了相同的栈,就会出现调用栈混乱。

4、线程的缺点

  • 性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变
  • 健壮性降低:编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。如果一个线程崩了,那么整个进程都会崩
  • 缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响
  • 编程难度提高:编写与调试一个多线程程序比单线程程序困难得多

5、线程异常

  • 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出

6、线程用途

  • 合理的使用多线程,能提高CPU密集型程序的执行效率
  • 合理的使用多线程,能提高IO密集型程序的用户体验(例如生活中我们一遍下载电视剧一边看电视剧,就是多线程运行的一种表现)

7、线程与进程的比较

  1. 进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们
  2. 另外,进程也是抢占处理机的调度单位,它拥有一个完整的虚拟地址空间。当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一地址空间
  3. 与进程相对应,线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源
  4. 线程只由相关堆栈(系统栈或用户栈)寄存器和线程控制表TCB组成。寄存器可被用来存储线程内的局部变量,但不能存储其他线程的相关变量

通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度,从而显著提高系统资源的利用率和吞吐量。因而近年来推出的通用操作系统都引入了线程,以便进一步提高系统的并发性,并把它视为现代操作系统的一个重要指标。

线程与进程的区别可以归纳为以下4点:
1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信–需要进程同步和互斥手段的辅助,以保证数据的一致性。
3)调度和切换:线程上下文切换比进程上下文切换要快得多。
4)在多线程OS中,线程不是一个可执行的实体

进程和线程的关系图:
初识多线程_第2张图片

8、线程控制

在Linux中,因为线程是用进程模拟出来的,所以Linux下不会给我们提供直接操作线程的接口,(线程就是个pcb,轻量级进程)所以久封装了一套线程库,用于线程控制。封装了一套库,就调度内核的执行流所有的接口都是库函数接口。通过用户线程(用户态实现的线程) 也是依靠内核中的轻量级进程(线程)执行流完成调度的
注意:创建线程用的是库函数,意味着我们用的是动态库里面的信息,而它被映射到共享区
共享区:线程是用户态创建的线程,是用到动态库的信息,所以线程所用到的东西几乎都是在共享区

8.1 POSIX线程库

  • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
  • 要使用这些函数库,要通过引入头文
  • 链接这些线程函数库时要使用编译器命令的“-lpthread”选项

8.2 创建线程

初识多线程_第3张图片
参数:

  • thread:返回线程ID
  • attr:设置线程的属性,attr为NULL表示使用默认属性
  • start_routine:是个函数地址,线程启动后要执行的函数,就相当于回调函数
  • arg:传给线程启动函数的参数

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

8.3 代码演示

#include
#include
#include
#include
void *thread_run(void* args)
{
	const char* id = (const char*)args;
	while(1)
	{
		printf("我是%s线程, %d\n", id, getpid());
		sleep(1);
	}
}

int main()
{
	//pthread_t 是一个封装的数据类型
	pthread_t tid;
	pthread_create(&tid, NULL, thread_run, (void*)"new thread");

	while(1)
	{
		printf("我是 main 主线程,%d\n", getpid());
		sleep(1);
	}
	return 0;
}

初识多线程_第4张图片
运行代码:
初识多线程_第5张图片
我们发现创建出来新的线程的PID和主线程(main)的PID一样,说明这个两个线程属于同一个进程

  • ldd pthread可以查看mypthread依赖哪一个库
    在这里插入图片描述

9、LWP概念

在单进程单线程之中,PID和LWP是一样的,而单进程多线程之中,linux引入了线程组的概念,线程组中每一个线程(轻量级进程)都存在一个进程描述符LWP,这个轻量级进程描述符就是用户级进程ID,是OS调度的最小单位,主线程的LWP和PID是一样的。Linux OS 调度的时候看的就是LWP,而不是PID

9.1 PID与进程LWP

每一个进程都有自己独立的PID,PID是用来标识每一个不同进程的,LWP是用来标识轻量级进程的。CPU在进行调度时用的是LWP(线程是调度的基本单位)PID与LWP的关系是(1:N)如果不同的LWP拥有同一个PID则它们属于同一个进程

9.2 查看LWP

可以用命令 ps -aL 查看线程LWP
初识多线程_第6张图片
同一进程有两个不同的线程,PID和LWP相同的就是主线程

9.3 线程ID

进程ID是每一个进程独有的ID,可以通过getpid函数获得,也可以通过 ps -ajx查看属于进程ID,而线程ID也是每一个线程独有的ID,在使用pthread_create函数时,可以通过第一个参数(输出型参数)获取线程ID,也可以使用函数pthread_self()获取。
初识多线程_第7张图片
初识多线程_第8张图片

  • 命令ps -aL查看到的LWP和用函数pthread_self()查看到的线程ID都是用来标识不同线程的,那么有什么区别呢?pthread_self得到的是非常大的一个数字,ps -aL得到的LWP是比较正常的,这是为什么呢?

LWP得到的线程ID是真正的内核线程ID。pthread_self得到是pthread库的线程ID,这个数实际上是一个地址,这个地址和pthread_ create函数产生的一个线程ID是同一个东西,通过这个地址,可以找到关于这个线程的基本信息,包括线程ID,线程栈,寄存器等属性。
LWP属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
内存单元的地址(pthread_self的返回值)即为新创建线程的线程ID,属于NPTL线程库的范畴

你可能感兴趣的:(Linux,Linux,线程,线程ID,LWP,POSIX线程库)