当一个程序开始执行时,从开始执行到执行完成这段时间在内存中的部分称为进程。
Linux是一个多任务操作系统,这意味着多个进程可以同时执行。 我们常用的单CPU计算机实际上在一个时间段只能执行一条指令。
那么Linux是如何实现多进程同时执行的呢?
原来Linux使用了一种叫做“进程调度”的方法:
首先,为每个进程分配一定的运行时间。 这个时间通常很短,短至毫秒。 然后按照一定的规则,选择众多进程中的一个运行,其他进程暂时等待。 当正在运行的进程超时,或者执行完后退出,或者由于某种原因被挂起时,Linux会重新调度并选择一个进程来运行,因为每个进程占用的时间段都很短。 从用户的角度来看,这就像多个进程同时运行。
在Linux中,每个进程在创建时都会被分配一个称为进程控制块(PCB)的数据结构。
PCB包含了大量对于系统调度和流程功能执行的重要信息。 最重要的是进程 ID。 进程ID也称为进程标识符。 它是一个非负整数。 Linux中可操作性 系统中进程的唯一标识符。
在最常用的I386架构上,非负整数的值为0~32767。 这也是我们可以获得的进程ID,也就是进程的ID号。
僵尸进程的产生
僵尸进程是已经结束但尚未从进程表中删除的进程。 僵尸进程过多会导致进程表表项变满,导致系统崩溃,但并不占用系统资源。
在进程的状态中,僵尸进程是一种非常特殊的一种。 它已经放弃了几乎所有的内存空间,没有任何可执行代码,并且无法调度。 它只在进程列表中保留一个位置来记录进程的状态。 退出状态等信息可以被其他进程收集;
另外,僵尸进程不再占用任何内存空间。 它需要它的父进程来收集它的尸体。 如果父进程没有安装信号处理函数,则调用wait或()等待子进程结束,并且不显式忽略该信号。 ,那么它就永远处于僵尸状态。
如果父进程结束,init进程会自动接管子进程并收其尸体,并且仍然可以清除。 但如果父进程是一个循环,不会结束,那么子进程就会一直处于僵尸状态。
僵尸进程产生的原因:
每个Linux进程在进程表中都有一个入口点(Entry),核心程序执行进程时使用的所有信息都存储在入口点处。 当使用ps命令查看系统中的进程信息时,看到的是进程表中的相关数据。
当fork系统调用创建新进程时,核心进程会在进程表中为新进程分配一个入口点,然后将相关信息存储在该入口点对应的进程表中。 这些信息之一是父进程的标识码。
当该进程完成其生命周期时,它将执行 exit() 系统调用。 这时,进程表中的原始数据将被进程的退出代码、执行过程中使用的CPU时间等数据所取代。 这些数据将被保留,直到系统将其传递给其父进程。
可以看出,僵尸进程出现在子程序终止之后、父进程读取数据之前。
如何避免僵尸进程
1、父进程通过wait等函数等待子进程结束,这会导致父进程挂起。
2、如果父进程很忙,可以用一个函数来安装,因为子进程结束后,父进程会收到信号,可以调用wait来回收。
3.如果父进程不关心子进程何时结束,那么可以使用“(),”来通知内核它对子进程的结束不感兴趣。 那么子进程结束后,内核会将其回收,不再发送给父进程。 信号。
4.还有一些技巧,就是fork()两次。 父进程fork出一个子进程,然后继续工作。 子进程派生孙进程,然后退出。 然后孙进程就被init接管了。 孙子进程结束后,init会回收它。 然而,子进程的回收必须自己完成。
进程PK线程
我们先打个比方。 多线程是一个十字路口。 多线程是一种平面传输系统。 它成本低,但红绿灯多,总是造成交通堵塞。 多线程是立交桥,价格昂贵,上下坡时更耗油,但不堵车。 这是一个抽象的概念。 相信大家看完后都会有这样的感受。
进程和线程是两个相对的概念。 一般来说,一个进程可以定义一个程序的实例()。
在Win32中,进程不执行任何操作,它只是占用应用程序使用的地址空间。
为了让一个进程完成一定量的工作,该进程必须至少占用一个线程。 该线程负责将代码包含在进程的地址空间中。
事实上,一个进程可以包含多个线程,这些线程同时执行进程地址空间中的代码。 为了做到这一点,每个线程都有自己的一组 CPU 寄存器和堆栈。
每个进程至少有一个线程在其地址空间中执行代码。 如果进程地址空间中没有线程执行代码,则该进程没有理由继续存在,系统将自动清除该进程及其地址空间。
多线程的实现原理
当一个进程创建时,它的第一个线程称为主线程( ),它是由系统自动生成的。 然后可以从此主线程生成其他线程,并且这些线程可以生成更多线程。
当运行多线程程序时,从外部看,线程似乎是同时运行的。 不是这种情况。 为了运行所有这些线程,操作系统为每个独立线程分配一些 CPU 时间。
单CPU操作系统以时间片轮换的方式向线程提供时间片()。 每个线程用完时间片后交出控制权,系统将CPU时间片分配给下一个线程。
由于每个时间片足够短,因此给人一种这些线程同时运行的错觉。 创建额外线程的唯一目的是最大化 CPU 时间。
多线程问题
使用多线程编程可以给程序员带来很大的灵活性,也使得解决原本需要复杂技术的问题变得更加容易。 但是,你不应该人为地将编写的程序分成一些片段,并让这些片段在自己的线程中执行。 这不是开发应用程序的正确方法。
线程很有用,但是当它们被使用时,它们会在解决旧问题的同时产生新问题。
例如,您想要开发一个文字处理程序,并希望打印功能作为单独的线程执行。 这听起来是个好主意,因为在打印时,用户可以立即返回并开始编辑文档。
但这样一来,在打印文档时,文档中的数据就可能被修改,打印出来的结果就不再是预期的了。
也许打印功能最好不要放在单独的线程中,但如果必须使用多线程,也可以考虑使用下面的方法来解决问题:
第一种方法是锁定正在打印的文档,让用户编辑其他文档,这样在打印完成之前不会对文档进行任何修改;
另一种方法可能更有效,即可以将文档复制到临时文件,打印临时文件的内容,并允许用户修改原始文档。
当打印包含该文档的临时文件时,该临时文件被删除。 从上面的分析我们可以看出,多线程在有助于解决问题的同时,也可能带来新的问题。 因此,有必要了解什么时候需要多线程,什么时候不需要多线程。 一般来说,多线程常用于在进行前台操作的同时需要进行后台计算或逻辑判断的情况。
线程的分类
在MFC中,线程分为两类,即工作线程和用户界面线程。 如果一个线程只完成后台计算,不需要与用户交互,那么可以使用工作线程;
如果需要创建处理用户界面的线程,则应该使用用户界面线程。
两者的主要区别在于MFC框架会给用户界面线程添加消息循环,以便用户界面线程可以处理自己的消息队列中的消息。
从这一点来看,如果需要在后台做一些简单的计算(比如重新计算电子表格),首先应该考虑使用工作线程,而当后台线程需要处理更复杂的任务时;
具体来说,当后台线程的执行流程随实际情况发生变化时,应使用用户界面线程,使其能够响应不同的消息。
线程优先级
当系统需要同时执行多个进程或线程时,有时需要指定线程的优先级。 线程的优先级一般是指该线程的基本优先级,即该线程相对于该进程的相对优先级和包含该线程的进程的优先级的组合。
操作系统按优先级排列所有活动线程。 系统中的每个线程都被分配一个优先级,范围从0到31。
在运行时,系统只是将CPU时间分配给第一个优先级31的线程,在该线程的时间片到期后,系统将CPU时间分配给下一个优先级31的线程。
当没有优先级为31的线程时,系统将开始为优先级为30的线程分配CPU时间,以此类推。
除了程序员在程序中改变线程的优先级外,有时系统在程序执行过程中也会自动、动态地改变线程的优先级。 这是为了确保系统对最终用户具有高度响应能力。
例如,当用户按下键盘上的某个键时,系统会暂时将处理该消息的线程的优先级提高2到3。CPU会执行该线程一个完整的时间片。 当时间片完成后,系统将线程的优先级减1。
线程同步
在使用多线程编程时,另一个非常重要的问题是线程同步。 所谓线程同步是指线程相互通信时避免破坏各自数据的能力。 同步问题是由前面提到的Win32系统的CPU时间片分配方式引起的。
虽然在某一时刻,只有一个线程占用CPU时间(单CPU时间),但没有办法知道线程何时何地被中断,所以如何保证线程之间不破坏彼此的数据就显得尤为重要。
在MFC中,可以使用4个同步对象来保证多个线程同时运行。 它们是临界区对象()、互斥对象()、信号量对象(CS)和事件对象()。
在这些对象中,临界区对象是使用最简单的。 它的缺点是只能同步同一进程内的线程。
另外,还有一种基本方法,本文称之为线性化方法,即在编程过程中,某些数据的写入操作是在一个线程中完成的。
这样,由于同一个线程中的代码总是按顺序执行,所以不可能同时重写数据。
总结:
在线程(相对于进程)中,线程是一个更接近于执行体的概念。 它可以与同一进程中的其他线程共享数据,但有自己的堆栈空间和独立的执行顺序。 这两者都可以提高程序的并发性,提高程序运行的效率和响应时间。
线程和进程在使用中各有优缺点:线程执行开销小,但不利于资源管理和保护; 而流程则恰恰相反。
根本区别只有一个:当使用多个进程时,每个进程都有自己的地址空间,而线程共享地址空间。 从速度上来说:线程生成快、线程间通信快、切换快等等,因为它们在同一个地址空间。 里面。
在资源利用率方面:线程的资源利用率较好,因为它们处于同一地址空间。 同步方面:线程在使用公共变量/内存时需要使用同步机制,因为它们处于同一个地址空间。 进程中:子进程是父进程的副本,子进程获得父进程的数据空间、堆和栈的副本。
最后,无论你是转行,无论你是初学者,还是高级,如果你想学习编程~
【值得关注首页】C/C++编程学习交流俱乐部!
有问必答、学习交流、技术讨论,还有海量的编程资源,零基础的视频也很棒~