【Linux】线程概念

210 阅读8分钟

【Linux】线程概念

本节重点

  1. 了解线程的概念,理解线程与进程的区别与联系。
  2. 学会线程控制、线程创建、线程终止、线程等待。
  3. 了解线程分离与线程安全概念。
  4. 学会线程同步。
  5. 学会使用互斥量、条件变量、posix信号量、以及读写锁。
  6. 理解基于读写锁的读者写作问题。

1. 线程概念

什么是线程?

  • 在一个程序里的执行路线就叫线程( thread )。更准确的定义是:线程是“一个在进程内部的控制序列”
  • 一切进程至少有一个执行线程
  • 线程在进程内部运行,本质就是在进程地址空间运行。
  • 在Linux系统中,CPU眼中,看到的PCB都要比传统的进程更加轻量化
  • 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流

理解线程概念?

在进程地址空间中:代码区,我们的代码是 由很多个函数构成的, 每一个函数都有地址,并且函数在进程中都是串行调用的。

无论是 多进程 还是 多线程,核心思想:将串行的执行流变为并行执行流。---在一定场景下就可以提高效率。

首先:要知道:进程 = 内核数据结构 + 代码和数据。创建一个进程,要创建进程PCB(task_struct)、进程地址空间(mm_struct)、页表、加载程序,构建映射等等,故创建一个进程的成本很高的--时空成本。

我们的目标不是多进程,而是多执行流并发执行。

创建执行流时,不创建进程地址空间,页表等等。只创建进程PCB(task_struct).让PCB指向同一个地址空间,看见同一份资源。这里的资源指进程地址空间,地址空间上的虚拟地址通过页表转化物理地址,拿到物理内存中数据即资源。将代码区分为3个区,让不同的PCB指向不同的区,CPU在调度PCB时,就形成了3个执行流并行执行。-----即多线程

问题:为什么要这样设计“线程”?

线程也需要被OS管理--如何管理-先描述再组织。(struct TCB)线程控制块--描述线程,然后通过各种数据结构组织起来。线程也需要被调度、切换、区分、创建、终止,管理流程与进程很像。例如:在进程调度算法完成后,在执行线程调度算法。---非常麻烦。

Linux的设计者认为,线程和进程都是执行流,具有极度相似性,没有必要单独设计数据结构和算法。--直接复用代码!用线程来模拟进程。有效复用进程的数据结构和算法。

Windows系统单独设计了线程模块!!!

问题:进程 vs 线程

什么是进程?

进程的内核角度:承担分配系统资源的基本实体。

不要站在调度的角度理解进程,要站在资源的角度理解进程。---进程进程本质就是容器。

当进程内执行的是一个操作系统时,被叫做内核级虚拟机的技术。

什么是线程?

线程:进程内的执行分支。

问题:调度

问题:多个执行流如何进行代码划分?

给不同的线程分不同的代码区,本质就是让不同线程,各自看到全部页表的子集。

如何做到?

void* newthreadrun(void* args)
{
	while(true)
	{
		cout<<"I am new thread"<<endl;
		sleep(1);
	}
}
int main()
{
	pthread_t tid;
	pthread_create(&tid,nullptr,newthreadrun,nullptr);
    //1. &tid:指向一个pthread_t类型的变量,该变量用于接收新创建线程的ID
    //2. nullptr:指向一个pthread_attr_t 类型的变量,该变量用于设置新线程的属性。如果传递nullptr,则新线程的属性为默认属性
    //3. newthreadrun:这是一个函数指针,指向新线程将要执行的函数,该函数的返回类型为void*
    //4. (void*)"thread-1": 这是一个指针,指向新·线程传递参数,如果不需要传参,可以传递nullptr。
	while(true)
	{
		cout<<" I am main thread"<<endl;
		sleep(1);
	}
}

从main函数开始执行,执行到pthread_create()函数,新线程执行newthreadrun()函数,主线程继续向下执行while循环。

函数编译完成后是若干个代码块(每一行代码都有地址:虚拟地址),函数名是该代码块的入口地址。

最后形成的是一个可执行程序,---所有函数都要按照地址空间统一编址

代码按照不同的函数把地址空间划分成不同的区域,每一个线程执行的是该函数的代码区域。并且每个函数都是一批连续的虚拟地址。则每个线程得到了一批虚拟地址,就可以根据自己所拥有的虚拟地址查页表(虚拟地址转换为物理地址)---即各自看到页表的子集,只能看到自己所拥有的内存区域。

2. 线程特点

线程的优点

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

与进程的切换相比,线程之间的切换需要操作系统做的工作少很多如何理解?

image-20241121100025401

总结:线程切换时,不需要将CPU内部硬件cache中数据丢弃,将冷数据变为热数据,

线程的缺点

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

线程异常

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

线程用途

  • 合理使用多线程,能提高CPU密集型程序的执行效率。
  • 合理使用多线程,能提高IO密集型程序的用户体验。(例如:生活中我们一边写代码,一边下载开发工具,就是多线程运行的一种表现)

3. 线程VS进程

  • 进程是资源分配的基本单位(进程设计强调的是独立
  • 线程是调度的基本单位(线程设计初时强调共享
  • 线程是共享进程数据,但也会占有自己的一部分数据
    1. 线程ID
    2. 一组寄存器(一组线程执行的上下文数据
    3. 栈(独立的栈结构---后面篇章讲)
    4. errno
    5. 信号屏蔽字
    6. 调度优先级

一个线程有哪些资源是需要线程独占的?

  • 线程的硬件上下文(CPU寄存器的值)---调度角度
  • 线程的独立栈结构---常规运行角度 --线程运行自己的函数时,所有的临时变量都存在自己的独立栈结构中。

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

  • 文件描述符表
  • 每种信号的处理方式
  • 当前的工作目录
  • 用户id和组id

4. 进程和线程的关系

两个概念:

  • 一个线程出现问题,导致其他线程也出问题,导致整个进程退出。-----线程安全问题
  • 多线程中,公共函数如果同时被多个线程同时进入----称:该函数被重入了

完结:线程概念入门完成!!!后面还需要多多实践,才可以加深对线程的理解与感悟。

[!IMPORTANT]

下一篇:【Linux】线程控制