三合一网站怎么建立,如何做网站规范,旅游网站设计模板图片,六安网站建设 220前言#xff1a;
#xff08;一#xff09;线程的概念
#xff08;二#xff09;线程的理解
#xff08;三#xff09;示例
#xff08;四#xff09;线程优缺点
线程的优点 线程的缺点
#xff08;五#xff09;线程和进程的切换
1.线程的切换
2.进程的切换…前言
一线程的概念
二线程的理解
三示例
四线程优缺点
线程的优点 线程的缺点
五线程和进程的切换
1.线程的切换
2.进程的切换
六线程的控制
1.POSIX 线程库
2.线程的创建
3.线程的查看
4.线程的等待
5.线程的退出
6.线程的取消 前言
在讲线程之前我们需要回顾之前的进程 程序运行后操作系统创建对应的PCB数据结构然后生成虚拟地址空间分配内存资源相关的代码和数据加载到物理内存中并通过页表映射关系和虚拟地址空间建立联系。 如此一看创建一个进程需要创建PCB、开辟地址空间等操作系统需要做很多工作所以创建一个进程所需的成本是很高的而想要在一个程序中使用多执行流如果创建多个进程的话那么程序运行的成本就太高了。
我们仔细观察进程的地址空间这里面的资源都是被一个task_struct的结构体所享有的页表也是其所独有的如果我们创建一个“子进程”但让它不去创建新的地址空间和页表映射而是和父进程PCB共享地址空间和页表程序运行时父进程就可以将部分代码给“子进程”这样父进程运行的同时“子进程”也可以运行而父进程能创建一个这样的“子进程”也能创建很多个 我们新创建出来的“子进程”它们的执行粒度要比“父进程”要细一些因为“父进程”需要执行全部代码而“子进程”只需执行部分代码。 正式的讲我们把这些“子进程”统称为线程
所以在Linux中线程在进程“内部”执行就是线程在进程的地址空间内运行那么为什么线程要在进程的地址空间中运行呢首先任何执行流要执行都要有资源。而地址空间是进程的资源窗口但线程有地址空间吗没有因为它没有建立和内存的联系所以线程需要依附于进程
在CPU角度CPU需要知道要执行的到底是进程还是线程吗不需要因为它的核心工作就是执行代码而进程和线程都是执行流都能执行代码。
所以我们创建的多个新的task_struct都能被CPU调度而不像之前只能进程被调度并且创建线程的成本远比进程低所以大大提高了CPU的效率。这些能被CPU调度、却没被操作系统分配资源的task_struct就叫做线程
一线程的概念
有了上面的引入我们重新定义线程和进程。
如何看待今天的进程呢进程 内核数据结构代码和数据 对于之前的进程来说它是内核中只有一个执行流的进程而现在的进程是内核中有多个执行流的进程。所以进程是承担系统资源的基本实体。
所以我们可以得出 线程是CPU调度的最小单位。进程是调度和分配的基本单位。 二线程的理解
由上面的图我们可以看出 每个进程都有自己独立的地址空间地址空间是指进程可访问的内存范围包括了数据段、代码段、堆、栈等。 因为进程之间的地址空间都是独立的所以不同进程之间的数据都是相互隔绝的一个进程无法直接访问另一个进程的地址空间进程间通信需要通过特定的机制来完成比如共享内存、消息队列、信号量等。 而在多线程的情况下每个线程并不具有独立的地址空间。线程是进程内的执行单元。多个线程共享同一个进程的地址空间所有线程都可以访问同一个进程的地址空间共享相同的全局变量和静态变量。 由于线程共享进程的地址空间因此线程之间可以更方便的进行数据共享和通信。但是也会带来一些潜在的问题比如线程之间可能会互相干扰需要通过同步机制来确保线程之间数据访问的正确性。 为什么线程的执行粒度比进程的低
资源共享和切换开销较小线程是在同一个进程内创建的轻量级执行单元它们共享进程的地址空间和资源。因此线程之间的切换开销通常比进程之间的切换开销小这使得线程更适合处理需要频繁切换和共享数据的任务。更快的创建和销毁速度线程的创建和销毁通常比进程快得多因为线程之间的资源共享较多创建线程时只需要复制一份线程控制块和栈空间即可。相比之下进程的创建和销毁需要复制整个地址空间资源消耗更大。更好的并发性和并行性由于线程共享进程的地址空间线程之间的通信和同步更加方便快捷。在多核处理器上多线程程序可以更好地利用多核资源进行并行计算提高系统的整体性能。更灵活的设计和实现多线程编程相对于多进程编程来说通常更灵活、更容易实现。线程之间可以直接共享内存不需要通过进程间通信的机制来进行数据传递和同步这简化了程序的设计和调试过程。
为什么线程调度的成本比进程的低
线程共享资源线程是进程的子集多个线程共享同一进程的资源包括内存空间、文件描述符等。因此在调度线程时不需要切换和分配额外的资源只需切换线程的上下文即可。线程切换开销小线程的切换只需要切换线程的上下文而进程的切换需要保存和恢复整个进程的状态包括地址空间、文件描述符等。线程调度更灵活由于线程共享进程的资源操作系统可以更灵活的调度线程不需要额外的资源分配和回收操作提高了调度的效率和性能。
三示例
#include iostream
#include pthread.h
#include unistd.hint gcnt 100;
void *ThreadRoutine(void *arg)
{const char *threadname (const char *)arg;while (true){std::cout I am a new thread,pid: getpid() gcnt: gcnt gcnt: gcnt std::endl;gcnt--;sleep(1);}
}int main()
{// 创建线程之前已经有进程了pthread_t tid1;pthread_create(tid1, nullptr, ThreadRoutine, (void *)thread 1);while (true){std::cout I am main thread,pid: getpid() ,gcnt: gcnt gcnt: gcnt std::endl;sleep(1);}return 0;
}运行程序 从上面我们可以看出来主进程和线程两个执行流它们的pid是相同的所调取的全局变量gcnt的物理地址、大小也是相同的。
由此可以看出线程和进程或者说线程和线程之间并不是父子进程的关系它们所用的地址空间也是同一个。
使用下面的命令我们可以查出当前正在运行的线程/进程的信息
while :; do ps -aL | head -1 ps -aL | grep testThread ; sleep 1;done可以看到运行程序后两个线程的PID是相同的但是LWP的值是不同的那么LWP是什么呢 LWPLightWeight Process轻量级进程在Linux系统中线程被实现为轻量级进程每个线程都有自己的线程控制块TCB用于保存线程的上下文信息。LWP之间可以共享进程的资源如地址空间、文件描述符等同时可以独立地进行调度和执行。 在这里的LWP指的是线程的编号。 四线程优缺点
线程的优点 创建一个新线程的代价要比创建一个新进程小的多与进程之间的切换相比线程之间的切换需要操作系统做的工作要少很多线程占用的资源要比进程少很多计算密集型应用为了能在多处理器系统上运行将计算分解到多个线程中实现I/O密集型应用为了提高性能将I/O操作重叠。线程可以同时等待不同的I/O操作 线程的缺点 性能损失 一个很少被外部事件阻塞的计算密集型线程往往无法与其他线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多那么可能会有会有较大的性能损失这里的性能损失指的是增加了额外的同步和调度开销而可用的资源不变。 健壮性降低 编写多线程需要更全面更深入的考虑在一个多线程程序里因时间分配上的细微偏差或者因为共享了不该共享的变量而造成不良影响的可能性是很大的换句话说线程之间是缺乏保护的。 缺乏访问控制 进程是访问控制的基本粒度在一个线程中调用某些OS函数会对整个进程造成影响。 编程难度提高 编写与调试一个多线程程序比单线程程序困难的多。 那为什么一个线程引发的错误要让整个进程来承担呢 共享地址空间在POSIX线程中同一进程内的所有线程共享相同的地址空间这意味着它们可以访问相同的内存区域包括全局变量、堆和栈。因此如果一个线程在共享内存区域中写入了错误的数据或者越界访问量某个数据结构这个错误将会影响到其他进程以及进程的运行状态。信号和异常处理在Linux中当一个线程引发了一个信号或者异常操作系统会将这个信号或异常发送给整个进程而不是仅仅发送给产生错误的线程。这是因为在POSIX线程中默认情况下所有线程共享相同的信号处理器设置。因此如果一个线程引发了一个信号整个进程都会接收到该信号这可能导致整个进程的状态被改变或者终止。资源共享进程中的所有线程共享各种资源包括打开的文件、网络连接、进程ID等。如果一个线程错误的关闭了一个文件描述符或者引发了其他类似的资源管理错误这会影响到整个进程的其他线程。取消线程在POSIX线程中可以使用pthread_cancel()函数来取消一个线程。如果一个线程被取消那么整个进程的其他线程也可能会受到影响具体取决于取消动作的方式和取消点的设置。 五线程和进程的切换
1.线程的切换
线程的切换是指从一个线程切换到另一个线程执行的过程。在多线程程序中操作系统负责管理线程的调度和切换确保多个线程能够合理地共享CPU资源。
线程的切换通常发生在以下几种情况下 时间片耗尽操作系统会为每个线程分配一定的时间片即CPU时间当线程执行的时间片耗尽时操作系统会进行线程切换将CPU时间分配给其他线程执行。这样可以保证每个线程都有机会执行。阻塞或等待当一个进程因为等待某些事件发生而被阻塞时操作系统会进程线程切换将CPU事件分配给其他可执行的线程。当等待的事件发生后被阻塞的线程会被唤醒重新进入就绪状态等待再次被调度执行。线程调用了阻塞系统调用某些系统调用例如I/O操作会导致线程进入阻塞状态等待系统完成相应的操作。在这种情况下操作系统会进行线程切换执行其他可执行的线程直到系统调用完成并唤醒线程。显示调用线程调度函数在某些情况下程序员可以显式地调用线程调度函数例如 pthread_yield() 函数来请求操作系统进行线程切换。 线程切换的具体实现由操作系统负责通常包括以下步骤 保存上下文保存当前正在执行线程的上下文信息包括程序计数器、寄存器状态、栈指针等。 选择下一个线程根据调度算法选择下一个要执行的线程可能会根据线程的优先级、调度策略等进行选择。 恢复上下文恢复被选中线程的上下文信息并将控制权转移到该线程的执行代码处 线程切换是操作系统中一项重要的工作其效率和性能对系统的整体性能有很大影响。因此操作系统的线程调度算法和线程切换的实现通常会进行优化以提高系统的性能和响应能力。
2.进程的切换
进程的切换是指从一个进程切换到另一个进程执行的过程。
进程切换通常涉及更多的开销和复杂性因为不同进程之间的地址空间是独立的需要进行上下文的完全切换。
进程的切换通常发生在以下几种情况下 时间片耗尽每个进程被分配一个时间片来执行当时间片耗尽时操作系统会强制进行进程切换将 CPU 时间分配给其他就绪态的进程。这样可以保证每个进程都有机会执行并避免了某个进程长时间占用 CPU 而导致其他进程无法执行的情况。 阻塞或等待当一个进程需要等待某些事件发生如 I/O 操作完成、信号等而被阻塞时操作系统会进行进程切换将 CPU 时间分配给其他就绪态的进程。当等待的事件发生后被阻塞的进程会被唤醒重新进入就绪态等待再次被调度执行。 进程调用了阻塞系统调用某些系统调用例如等待 I/O 完成的系统调用会导致进程进入阻塞状态等待系统完成相应的操作。在这种情况下操作系统会进行进程切换执行其他可执行的进程直到系统调用完成并唤醒进程。 优先级调度如果有更高优先级的进程需要执行操作系统可能会进行进程切换将 CPU 时间分配给优先级更高的进程以提高系统对高优先级任务的响应速度。 抢占式调度在支持抢占式调度的系统中高优先级进程可以在任何时候抢占低优先级进程的 CPU 时间导致进程切换。这样可以确保高优先级任务的及时执行。 以下是进程切换的一般过程 保存上下文操作系统会保存当前进程的所有寄存器状态、程序计数器和其他必要的执行环境信息以便稍后恢复。 选择下一个进程操作系统根据调度算法选择下一个要执行的进程。调度算法可能基于进程的优先级、调度策略等进行选择。 保存进程状态操作系统会保存下一个进程的状态信息。这包括进程的寄存器状态、程序计数器以及其他与进程执行相关的状态。 切换地址空间由于不同进程具有不同的地址空间因此在切换进程时操作系统需要将当前的地址空间切换为下一个进程的地址空间。这通常涉及修改内存管理单元MMU或处理器的地址转换表。 恢复上下文操作系统会恢复被选中进程的上下文信息并将控制权转移到该进程的执行代码处。 进程切换通常比线程切换开销更大因为进程拥有独立的地址空间需要额外的资源来管理和维护。此外进程切换还可能涉及到内核态和用户态之间的切换因此开销更大。然而与线程切换相比进程切换的优点在于进程之间的隔离性更好更不容易相互影响。
六线程的控制
1.POSIX 线程库 POSIX线程库 pthreads是一种标准的线程库定义了一组用于创建和管理线程的API。POSIX是Portable Operating System Interface的缩写是一个IEEE标准定义了操作系统接口的标准包括线程、进程、信号、文件系统等。 2.线程的创建 #include pthread.hint pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);作用 用于创建一个进程。 参数 pthread_t *thread:一个指向pthread_t 类型的指针用来存储新创建线程的标识符。const pthread_attr_t *attr:一个指向pthread_attr_t类型的指针用于指定新线程的属性。通常可以传入NULL表示使用默认属性。void *(*start_routine)(void*):一个函数指针指向新线程将要执行的函数。函数的返回值和参数必须都是void *类型。void *arg:传递给start_routine函数的参数。 返回值 函数执行成功将会创建一个新的线程并将其标识符存储在thread指针指向的内存当中返回0如果执行失败将返回错误编号。 3.线程的查看
查看线程既可以在Linux终端中输入ps命令进程查看也可以在程序中调用系统函数pthread_self()来查看。 #include pthread.hpthread_t pthread_self(void);作用 在线程中获取自身的线程ID以便于进程一些线程相关的操作比如打印线程信息、线程同步等。 返回值 返回线程自身的线程ID。 4.线程的等待
在线程编程中有时候需要等待一个线程完成其任务然后才能继续执行其他操作。在 POSIX 线程库中可以使用 pthread_join() 函数来等待一个线程结束。 #include pthread.hint pthread_join(pthread_t thread, void **retval);作用 调用pthread_join函数将会阻塞当前进程直到指定的线程结束为止。当目标线程结束时当前线程会继续执行并且获取目标线程的返回值。 参数 pthread_t thread:要等待的进程ID。void **retval:用于存储目标进程的返回值。如果不关心目标线程返回值可以传入NULL 返回值 如果pthread_join函数成功返回即等待目标线程结束并成功获取了目标线程的返回值返回值为0。如果pthread_join函数返回一个非零值表示等待目标线程结束时出现了错误此时可以根据返回值判断具体的错误类型。 5.线程的退出
在进程当中我们学习了用exit来结束一个进程而对于线程来说也同样适用只不过对线程使用exit函数会导致整个进程一起退出。
另外还可以利用return来让线程退出。
我们还可以先程序中调用pthread_exit()函数来让单个线程退出它不会让整个进程一起退出。 #include pthread.hvoid pthread_exit(void *retval);作用 在某个线程中调用该函数可以终止当前线程的执行并返回指定的值。 参数 void *retval:可选参数表示线程的退出状态类型为void*。该参数可以用于向调用pthread_join函数的线程传递一个退出状态值。如果不需要向其他线程传递退出状态可以将retval设为NULL。 6.线程的取消
线程的取消是指在某个线程执行过程中另一个线程主动终止目标线程的执行。POSIX线程库提供了pthread_cancel()函数来取消一个线程的执行。 #include pthread.hint pthread_cancel(pthread_t thread);作用 取消目标线程的执行。 参数 pthread_t thread:thread_t类型表示要取消执行的进程ID。 返回值 如果成功发送了取消请求给目标进程返回0 如果发送请求时出现了错误返回非零值。 以上就是对线程的初理解啦写了7K字觉得写的不错的小伙伴可以点个赞~