目录一、线程概念二、pthread原生线程库三、线程 VS 进程3.1 线程切换轻量化原理3.2 线程私有数据3.3 线程优缺点1、线程优点2、线程缺点四、线程相关函数及功能4.1 线程创建--create4.2 获取自身线程id--self4.3 线程取消--cancel4.4 线程终止--exit4.5 线程等待--join4.6 线程分离--detach五、完结一、线程概念一个进程需要访问的大部分资源如代码数据new\malloc 开辟的空间数据命令行参数环境变量等甚至是系统调用的内核代码。进程都是通过虚拟地址空间来访问的也就是进程地址空间换而言之进程地址空间就是进程的资源窗口。进程的创建是复杂的首先要创建进程的PCB,进程地址空间页表再将进程自身的代码和数据换入内存中并进行虚拟地址和物理地址的映射。接着将进程的状态改为R运行状态再把进程的 PCB 挂在运行队列中排队。但如果现在已经存在一个进程了我们把这个进程的PCB复制多份然后让所有 进程 的 PCB 全都指向同一个虚拟地址空间然后通过技术手段将这份虚拟地址空间合理分配给每个进程当 CPU 调度该 进程 时每个 进程 只会调用其中一部分的代码和数据也就是执行我们所要完成的一小部分。 这样既不影响最终结果也会减轻单个进程的负担相当于把一个任务拆分成几个小任务完成。我们把这种 进程 比传统进程更轻量化的进程叫做线程。线程是在进程内部执行比进程更加轻量化的一种执行流二、pthread原生线程库在Linux中如果Linux真的支持线程那么就会存在大量的线程操作系统要对这些线程进行管理那就是进行 先描述再组织 。即操作系统要创建一个结构体struct TCB这样就可以将这些线程用链表的形式管理起来。1、 线程又是进程内部的一种执行流所以要将线程和进程进行耦合。2、 线程同样也需要被调度所以对线程的调度有属于自己的调度算法。进程的 PCB 和线程的 TCB 所使用的属性其实是差不多的都需要pid、状态、调度优先级…并且两者都是需要被调度被运行的所以其实可以用PCB来代替TCB。PCB包含操作系统管理进程所需的各种信息将其用于线程管理时可把线程视为轻量级进程。这样能以统一的视角看待进程和线程。当操作系统进行线程调度时依据PCB中的优先级字段选择优先级最高的线程执行就如同对进程的调度一样。我们将这种直接创建PCB然后让所有PCB指向同一个地址空间在对资源进行分配的线程实现方式称为Linux中线程的实现方案我们也将这种线程称为轻量级进程在Linux中没有真正意义上的线程Linux是用轻量级进程来充当线程。但这是Linux OS自身的独特设计对于我们用户来说只认识进程和线程。所以我们在Linux中封装了一层软件层。该软件层向下调用轻量级进程的相关接口向上为用户提供线程的控制接口这层软件层叫做pthread 原生线程库所以我们在使用线程时需要主动链接pthread库。可以通过 ps -al 来查看系统线程三、线程 VS 进程1、 线程是进程内部的一种执行流线程比进程更加轻量化2、线程是操作系统调度的基本单位而进程是资源分配的基本单位3、进程拥有独立的进程地址空间和页表 而线程是共享进程的进程地址空间页表和其他资源4、进程的栈是由进程地址空间维护的而线程的栈是由pthread维护映射到共享区的声明共享区是虚拟地址空间的一段区域共享区通过虚拟地址隔离使线程的物理内存完全独立3.1 线程切换轻量化原理CPU在调度执行某一行代码时计算机会将该行周围的代码全部加载到 CPU 的 cache 缓存中。而 CPU 需要访问内存时优先访问的是缓存。如果数据在缓存中访问速度会非常快如果不在那就从较慢的主内存中加载。而 cache 缓存是根据虚拟地址来快速查找数据的一旦进程切换那么同一个虚拟地址对应的物理地址就会不一样所以需要重新对数据进行热加载即将数据加载到 cache 缓存中但对于线程切换来说下一个线程极有可能还是访问的这些代码和数据也就是说不需要更改虚拟地址因为资源所在的物理地址是不变的除此之外在 CPU 中还存在很多寄存器来存储进程/线程的上下文存在一些寄存器保存的内容执行地址空间和页表。对于线程来说地址空间和页表是相同的这也意味着线程切换时我们不需要将所有线程的寄存器全部切换走只需将少部分保存临时数据的寄存器切换即可那么轻量化的方面就体现出来了-:需要切换的寄存器少不需要重新更新cache3.2 线程私有数据因为线程是进程的内部执行流所以线程和进程共享大部分数据如代码段数据段全局数据等都是共享的。除了这些还包括以下资源1、共享当前进程的文件描述符表2、每种信号的处理方式3、当前工作目录4、用户id和组id当然线程也会有自己私有的数据具体如下-:1、每个线程拥有独立的寄存器硬件上下文。即当线程被切换出去时操作系统会把寄存器中的值(上下文)保存到该线程专属的数据结构中因为线程是被独立调度的所以每个线程必须拥有独立的上下文比如运行中产生的临时数据。2、线程都有独立栈结构。一般地址空间中的栈属于主进程然后通过在堆上申请空间充当其他新线程的栈空间3、线程ID、信号屏蔽字、调度优先级、errno等(注:errno是一个全局变量通过errno可以确定错误的类型)3.3 线程优缺点1、线程优点1、线程的创建删除切换比进程更加简单轻量线程占用的系统资源比进程更少2、对于计算密集型应用线程可以利用多处理器并行的特点将计算分解成多个线程并行处理提高效率3、对于 I/O 密集型应用可以将 I/O 操作进行重叠以线程同时等待不同的 I/O 操作提高性能2、线程缺点如果线程的数量比处理器的物理核心多相当于增加了额外的同步和调度开销而可用的资源不变导致性能损失(CPU 的物理核心不是无限的)线程会导致整体程序的健壮性降低。如果存在一些错误导致线程异常会导致进程整体异常退出线程缺乏访问控制进程是访问控制的基本粒度。对于线程OS 中并没有提供相关的方法进行线程访问控制编写难度高线程出现问题通常是很难进行排查的。四、线程相关函数及功能4.1 线程创建--create【函数接口】int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); Compile and link with -pthread//编译时需要链接pthread原生线程库 返回值成功返回0失败返回错误码thread输出型参数返回线程id。attr:设置线程属性为NULL表示使用默认属性。start_routine新线程执行方法的函数地址。arg传给start_routine函数的参数。错误检查传统的一些函数是成功返回0失败返回-1并且对全局变量errno赋值以指示错误。【实例】void* ThreadRoution(void* arg) { std::cout I am new thread std::endl; const char* threadname (const char*)arg; std::cout threadname std::endl; } int main() { pthread_t tid; pthread_create(tid, nullptr, ThreadRoution, (void*)thread-1); std::cout I am main thread std::endl; sleep(1); return 0; }4.2 获取自身线程id--selfpthread_t pthread_self(void);4.3 线程取消--cancelint pthread_cancel(pthread_t thread); 返回值成功返回0失败返回错误码4.4 线程终止--exit线程退出有3种方式return返回、pthread_cancel取消线程、pthread_exit()线程终止。不能调用exit()函数该函数用于进程退出一般调用所以相关线程将全部退出【线程终止函数原型】void pthread_exit(void *retval);retval:retval不要指向一个局部变量。返回值无返回值跟进程一样线程结束的时候无法返回到它的调用者自身。pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,即形参retval必须是全局的或者是malloc分配的。不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时会发现线程函数已经退出了就是意思这个栈区的指针已经销毁了那就是野指针的问题了。4.5 线程等待--join一般情况下主线程需要对新线程进行等待否则新线程就会一直占用资源会导致类似于进程的“僵尸”问题导致内存泄漏int pthread_join(pthread_t thread, void **retval); thread需要等待的线程id retval:指向待等待线程的返回值4.6 线程分离--detach默认情况下新创建的线程是 joinable 的线程退出后需要对其进行 pthread_join 操作否则无法释放资源从而造成系统泄漏。如果不关心线程的返回值join 是一种负担这个时候我们可以将线程设置为分离状态告诉系统当线程退出时自动释放线程资源!线程组内其他线程对目标线程进行分离也可以是线程自己分离但 joinable 和分离是冲突的一个线程不能既是 joinable 又是分离的int pthread_detach(pthread_t thread);线程分离后是可以cancel的但不能join ! !五、完结