线程与进程_内容充实&实例讲解&详细汇总 ❥ 内容借鉴:青灯夜游、囧辉、zhouchunyue 【操作系统】相关原创:《操作系统之进程管理——生产者&消费者实现线程同步&互斥【详细代码&原理解析】》 操作系统之进程管理–生产者&消费者实现线程同步&互斥【详细代码&原理解析】_夏旭的博客-CSDN博客 线程 线程的概念: 线程是操作系统能够进行独立运行调度的最小单位,包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程可以并发多个线程,每条线程并行执行不同的任务。线程是独立调度和分派的基本单位。线程可以作为操作系统内核调度的内核线程,如Win32线程;或者是由用户进行自行调度的用户线程,如Linux平台的POSIX Thread;或由内核与用户进程,如Win7的线程,进行混合调度。同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。 线程的特点(四大属性): 在多线程OS中,通常是一个进程中包含多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体,线程有以下四个属性:轻型实体:线程中的实体基本上不拥有系统资源,只是拥有一点必不可少的、能保证独立运行的资源。线程的实体包括程序、数据和TCB(Thread Control Block)。线程是动态概念,它的动态特性由线程控制块TCB描述。独立调度和分派的基本单位:在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故切换非常迅速且开销小(同一进程中的)。可并发执行:…甚至允许一个进程中所有线程都能并发执行;不同进程中的线程也能并发执行,充分利用和发挥了 处理机与外围设备并行工作的能力。共享进程资源:在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。 多线程 多线程的概念: 线程是操作系统能够进行运算调度的最小单位;它被包含在进程之中,是进程中的实际运作单位。多线程,是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。简单来说:线程是程序中一个单一的顺序控制流程;而多线程就是在单个程序中同时运行多个线程来完成不同的工作。多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。多线程是在同一时间需要完成多项任务的时候实现的。 多线程的优点: 多线程技术可以加快程序的运行速度,使程序的响应速度更快,因为用户界面可以在进行其它工作的同时一直处于活动状态可以把占据长时间的程序中的任务放到后台去处理,同时执行其他操作,提高效率当前没有进行处理的任务时可以将处理器时间让给其它任务可以让同一个程序的不同部分并发执行,释放一些珍贵的资源如内存占用等等可以随时停止任务可以分别设置各个任务的优先级以优化性能 多线程的缺点: 因为多线程需要开辟内存,而且线程切换需要时间因此会很消耗系统内存。线程的终止会对程序产生影响由于多个线程之间存在共享数据,因此容易出现线程死锁的情况对线程进行管理要求额外的 CPU开销。线程的使用会给系统带来上下文切换的额外负担。 cpu的多线程的用处 让同一个处理器上的多个线程同步执行并共享处理器的执行资源;可最大限度地实现宽发射、乱序的超标量处理;提高处理器运算部件的利用率;缓和由于数据相关或Cache未命中带来的访问内存延时。 处理器多线程的作用 可以为高速的运算核心准备更多的待处理数据,减少运算核心的闲置时间;解决了负载均衡问题,充分利用了CPU资源,提高CPU的使用率;可以同时完成几件事情而不互相干扰,缩短了处理大量的IO操作时或处理的情况需要花费的大量时间。 CPU又称中央管理器,是一块超大规模的集成电路,是一台计算机的运算核心和控制核心。CPU的主要组成包括了运算器和控制器。 中断是指计算机由于异常事件,或者一些随机发生需要马上处理的事件,引起CPU暂时停止现在程序的执行,转向另一服务程序去处理这一事件,处理完毕再返回原程序的过程。 cpu主要性能指标 一、主频 也就是CPU的时钟频率,简单地说也就是CPU的工作频率。 一般说来,一个时钟周期完成的指令数是固定的,所以主频越高,CPU的速度也就越快了。不过由于 各种CPU的内部结构也不尽相同,所以并不能完全用主频来概括CPU的性能。 主频和实际的运算速度是有关的,只能说主频仅仅是CPU性能表现的一个方面,而不代表CPU的整体性能。 二、外频 外频是CPU的基准频率,单位是MHz。CPU的外频决定着整块主板的运行速度。通俗地说,在 台式机中,所说的超频,都是超CPU的外频 (当然一般情况下,CPU的倍频都是被锁住的) 相信这点是很好理解的。 但对于服务器CPU来讲,超频是绝对不允许的。前面说到CPU外频决定着主板的运行速度,两者是同步运行的,如果把服务器CPU超频了,改变了外频,会产生异步运行,这样会造成整个服务器系统的不稳定。 三、前端总线FSB频率 前端总线(FSB)频率,即总线频率,是直接影响CPU与内存直接数据交换速度。有一条公式可以计算,即:数据带宽=(总线频率×数据带宽)/8,数据传输最大带宽取决于所有同时传输的数据的宽度和传输频率。 四、CPU的位和字长 位:在数字电路和电脑技术中采用二进制,代码只有“0”和“1”,其中无论是“0”或是“1”,在CPU中都是一“位”。 字长:电脑技术中对CPU在单位时间内 (同一时间) 能一次处理的二进制数的位数叫字长。 所以能处理字长为8位数据的CPU通常就叫8位的CPU。同理32位的CPU就能在单位时间内处理字长为32位的二进制数据。 8位的CPU一次只能处理一个字节,而32位的CPU一次就能处理4个字节,同理字长为64位的CPU一次可以处理8个字节。 五、倍频系数 倍频系数是指CPU主频与外频之间的相对比例关系。在相同的外频下,倍频越高CPU的频率也越高。但实际上,在相同外频的前提下,高倍频的CPU本身意义并不大。 这是因为CPU与系统之间数据传输速度是有限的,一味追求高倍频而得到高主频的CPU就会出现明显的“瓶颈”效应——CPU从系统中得到数据的极限速度不能够满足CPU运算的速度。 六、缓存 缓存大小也是CPU的重要指标之一,而且缓存的结构和大小对CPU速度的影响非常大,CPU内缓存的运行频率极高,一般是和处理器同频运作,工作效率远远大于系统内存和硬盘。 实际工作时,CPU往往需要重复读取同样的数据块,而缓存容量的增大,可以大幅度提升CPU内部读取数据的命中率,而不用再到内存或者硬盘上寻找,以此提高系统性能。但是由于CPU芯片面积和成本的因素来考虑,缓存都很小。 七、超线程 可以同时执行多重线程,就能够让CPU发挥更大效率,那就是超线程(Hyper-Threading) 技术,超线程技术减少了系统资源的浪费,可以把一颗CPU模拟成两颗CPU使用,在同时间内更有效地利用资源来提高性能。 八、制程技术 制程越小发热量越小,这样就可以集成更多的晶体管,CPU效率也就更高。 提升cpu性能的方法 关闭没用的程序;超频或升级CPU;清理系统垃圾;提高CPU的时钟频率和增加缓存容量;通过超频来使得CPU频率变强等等。 线程之间的通信方式 一、消息队列 最常用,也最灵活,通过自定义数据结构,可以传输复杂和简单的数据结构。 在Windows程序设计中,每一个线程都可以拥有自己的消息队列(UI线程默认自带消息队列和消息循环,工作线程需要手动实现消息循环),因此可以采用消息进行线程间通信sendMessage,postMessage。 定义消息#define WM_THREAD_SENDMSG=WM_USER+20; 添加消息函数声明afx_msg int OnTSendmsg(); 添加消息映射ON_MESSAGE(WM_THREAD_SENDMSG,OnTSM) 添加OnTSM()的实现函数; 在线程函数中添加PostMessage消息Post函数 二、使用全局变量 进程中的线程间内存共享,这是比较常用的通信方式和交互方式。 【注】:定义全局变量时最好使用volatile来定义,以防编译器对此变量进行优化。 三、使用事件CEvent类 Event 对象有两种状态:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。1) 创建一个 CEvent类 的对象:CEvent threadStart;它默认处在未通信状态;2) threadStart.SetEvent(); 使其处于通信状态;3) 调用 WaitForSingleObject() 来监视 CEvent对象。 线程安全与不安全 概念: 线程安全: 就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全: 就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。 运用: 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码,如果每次运行结果和单线程运行结果一样的话,而且其他变量的值也会和预期一样,就是线程安全的。 假如你有两个一摸一样的银行卡,卡上有1000块钱,而你和朋友在同一时间取钱。如果是线程不安全的情况下,两人都能取出1000。而如果线程安全的话,只能一个人同时操作一个账户,当这个账户正在被操作时,是被锁起来的,不给别人动的,只能你自己动,你动完了别人才能动。 为什么会有线程安全问题: 线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量,静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。 例子: 一个ArrayList类,在添加一个素的时候,它可能会有两步来完成:1.在items[Size]的位置存放此素;2.增大Size的值。在单线程运行的情况下,如果Size=0,添加一个素后,此素在位置0,而且Size=1;而如果是在多线程的情况下,比如有两个线程,线程A先将素存放在位置0。但是此时CPU调度线程A暂停,线程B得到运行的机会。线程B也向此ArrayList添加素,因为此时Size仍然等于0(这里注意,我们假设的是添加一个素是两个步骤,而线程A仅仅完成了步骤1),所以线程B也将素存放在位置0.然后线程A和线程B都继续运行,都增加Size的值。那好,我们来看看ArrayList的情况,素实际上只有一个,存放在位置0,而Size却等于2.这就是线程不安全了。 线程兼容和线程对立 线程兼容 线程兼容类不是线程安全的,但是可以通过正确使用同步而在并发环境中安全地使用。这可能意味着用一个 synchronized 块包围每一个方法调用,或者创建一个包装器对象,其中每一个方法都是同步的(就像 Collections.synchronizedList() 一样);也可能意味着用 synchronized 块包围某些操作序列。为了最大程度地利用线程兼容类,如果所有调用都使用同一个块,那么就不应该要求调用者对该块同步。这样做会使线程兼容的对象作为变量实例包含在其他线程安全的对象中,从而可以利用其所有者对象的同步。许多常见的类是线程兼容的,如集合类 ArrayList 和 HashMap 、 java.text.SimpleDateFormat 、或者 JDBC 类 Connection 和 ResultSet 。 线程对立 线程对立类是那些不管是否调用了外部同步都不能在并发使用时安全地呈现的类。线程对立很少见,当类修改静态数据,而静态数据会影响在其他线程中执行的其他类的行为,这时通常会出现线程对立。线程对立类的一个例子是调用 System.setOut() 的类。 线程池 如果我们在方法中直接new一个线程来处理,当这个方法被调用频繁时就会创建很多线程,不仅会消耗系统资源,还会降低系统的稳定性。 默认情况下,即使是核心线程也只能在新任务到达时才创建和启动。但是我们可以使用 prestartCoreThread(启动一个核心线程)或 prestartAllCoreThreads(启动全部核心线程)方法来提前启动核心线程。
线程池的好处: 降低资源消耗。通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。 增加线程的可管理型。线程是稀缺资源,使用线程池可以进行统一分配,调优和监控。 线程池的核心属性: threadFactory(线程工厂):用于创建工作线程的工厂。 corePoolSize(核心线程数):当线程池运行的线程少于 corePoolSize 时,将创建一个新线程来处理请求,即使其他工作线程处于空闲状态。 workQueue(队列):用于保留任务并移交给工作线程的阻塞队列。 maximumPoolSize(最大线程数):线程池允许开启的最大线程数。 handler(拒绝策略):往线程池添加任务时,将在下面两种情况触发拒绝策略:1)线程池运行状态不是 RUNNING;2)线程池已经达到最大线程数,并且阻塞队列已满时。 keepAliveTime(保持存活时间):如果线程池当前线程数超过 corePoolSize,则多余的线程空闲时间超过 keepAliveTime 时会被终止。 线程池的运作流程:
线程池的五个状态和状态流转: 五个状态: RUNNING:接受新任务并处理排队的任务。 SHUTDOWN:不接受新任务,但处理排队的任务。 STOP:不接受新任务,不处理排队的任务,并中断正在进行的任务。 TIDYING:所有任务都已终止,workerCount 为零,线程转换到 TIDYING 状态将运行 terminated() 钩子方法。 TERMINATED:terminated() 已完成。 状态流转:
线程池常见的几种拒绝策略: AbortPolicy:中止策略。默认的拒绝策略,直接抛出 RejectedExecutionException。调用者可以捕获这个异常,然后根据需求编写自己的处理代码。 DiscardPolicy:抛弃策略。什么都不做,直接抛弃被拒绝的任务。 DiscardOldestPolicy:抛弃最老策略。抛弃阻塞队列中最老的任务,相当于就是队列中下一个将要被执行的任务,然后重新提交被拒绝的任务。如果阻塞队列是一个优先队列,那么“抛弃最旧的”策略将导致抛弃优先级最高的任务,因此最好不要将该策略和优先级队列放在一起使用。 CallerRunsPolicy:调用者运行策略。在调用者线程中执行该任务。该策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将任务回退到调用者(调用线程池执行任务的主线程),由于执行任务需要一定时间,因此主线程至少在一段时间内不能提交任务,从而使得线程池有时间来处理完正在执行的任务。 线程池的大小配置多少合适: 要想合理的配置线程池大小,首先我们需要区分任务是计算密集型还是I/O密集型。 对于计算密集型,设置 线程数 = CPU数 + 1,通常能实现最优的利用率。 对于I/O密集型,网上常见的说法是设置 线程数 = CPU数 * 2 ,这个做法是可以的,但个人觉得不是最优的。 在我们日常的开发中,我们的任务几乎是离不开I/O的,常见的网络I/O(RPC调用)、磁盘I/O(数据库操作),并且I/O的等待时间通常会占整个任务处理时间的很大一部分,在这种情况下,开启更多的线程可以让 CPU 得到更充分的使用,一个较合理的计算公式如下: 线程数 = CPU数 * CPU利用率 * (任务等待时间 / 任务计算时间 + 1) 例如我们有个定时任务,部署在4核的服务器上,该任务有100ms在计算,900ms在I/O等待,则线程数约为:4 * 1 * (1 + 900 / 100) = 40个。 当然,具体我们还要结合实际的使用场景来考虑。如果要求比较精确,可以通过压测来一个合理的值。 C++面试高频宝典(线程与进程) ●多进程和多线程的区别 进程它是具有独立地址空间的。优点就是隔离度好、稳定,因为它是操作系统管理的,进程和进程之间是逻辑隔离的,只要操作系统不出问题的话,一个进程的错误一般不会影响到其它进程;缺点就是信息资源共享麻烦。而线程只是进程启动的执行单,它是共享进程资源的,创建销毁、切换简单,速度很快,占用内存少,CPU利用率高。但是需要程序员管控的东西也比较多,相互影响出问题的机率较大,一个线程挂掉将导致整个进程挂掉,所以从程序员的角度来讲,我们只能看到某种代码是线程安全的,而没有说进程安全的。 ●在进程和线程上,应该怎么选择 我们平时在写代码的时候一般使用线程会比较多,像需要频繁创建销毁的,要处理大量运算、数据,又要能很好的显示界面和及时响应消息的优先选择多线程,因为像这些运算会消耗大量的CPU,常见的有算法处理和图像处理。还有一些操作允许并发而且有可能阻塞的, 也推荐使用多线程. 例如SOCKET, 磁盘操作等等。进程一般来说更稳定,而且它是内存隔离的,单个进程的异常不会导致整个应用的崩溃,方便调试,像很多服务器默认是使用进程模式的。 ●线程之间是如何通信的 一个是使用全局变量进行通信,还有就是可以使用自定义的消息机制传递信息。其实因为各个线程之间它是共享进程的资源的,所以它没有像进程通信中的用于数据交换的通信方式,它通信的主要目的是用于线程同步,所以像一些互斥锁啊临界区啊CEvent事件对象和信号量对象都可以实现线程的通信和同步。 ●进程之间是如何通信的 进程间的通信方式有PIPE管道,信号量,消息队列,共享内存,还可以通过 socket套接字进行通信。根据信息量大小的不同可以分为低级通信和高级通信,在选择上,如果用户传递的信息较少.或是需要通过信号来触发某些行为的,一般用信号机制就能解决,如果进程间要求传递的信息量比较大或者有交换数据的要求,那么就要使用共享内存和套接字这些通信方式。 名词解释: 管道:其实是存在于内存中的一种特殊文件,它不属于文件系统,有自己的数据结构,根据使用范围还可分为无名管道和命名管道。 共享内存:是通过将共享的内存缓冲区直接附加到进程的虚拟地址空间中来实现的,它是利用内存缓冲区直接交换信息,不需要复制,很快捷、信息量大。 消息队列缓冲:是由系统调用函数来实现消息发送和接收之间的同步,它允许任意进程通过共享消息队列来实现进程间通信.但是信息的复制需要耗费大量CPU,所以不适用于信息量大或操作频繁的场合。 ●线程同步和线程异步 同步是指一个线程要等待另一个线程执行完之后才开始执行当前的线程。 异步是指一个线程去执行,它的下一个线程不必等待它执行完就开始执行。 一般一个进程启动的多个不相干线程,它们之间的相互关系就为异步,比如游戏有图像和背景音乐,图像是由玩家操作的 而背景音乐是系统循环播放,它们两个线程之间没什么关系各干各的,这就是线程异步。至于同步的话指的是多线程同时操作一个数据,这个时候需要对数据添加保护,这个保护就是线程的同步 同步使用场景:对于多个线程同时访问一块数据的时候,必须使用同步,否则可能会出现不安全的情况,有一种情况不需要同步技术,那就是原子操作,也就是说操作系统在底层保证了操作要么全部做完,要么不做。 异步的使用场景:当只有一个线程访问当前数据的时候。比如观察者模式,它没有共享区,主题发生变化后通知观察者更新,主题继续做自己的事情,不需要等待观察者更新完成后再工作。 ●多线程同步和互斥有几种实现方法,分别适用什么情况 线程同步的话有临界区,互斥量,信号量,事件。 临界区适合一个进程内的多线程访问公共区域或代码段时使用。 互斥量是可以命名的,也就是说它可以适用不同进程内多线程访问公共资源时使用。所以在选择上如果是在进程内部使用的话,用临界区会带来速度上的优势并且能够减少资源占用量。 信号量与临界区和互斥量不同,它是允许多个线程同时访问公共资源的,它相当于操作系统的PV操作,它会事先设定一个最大线程数,如果线程占用数达到最大,那么其它线程就不能再进来,如果有部分线程释放资源了,那么其它线程才能进来访问资源。 事件是通过通知操作的方式来保持线程同步。 注意:互斥量,事件,信号量都是内核对象,可以跨进程使用。 ●C++多线程有几种实现方法 直接使用WIN32 API CreateThread,或者用C运行库_beginthread创建线程,MFC的话用AfxBeginThread. 还有就是运用第三方线程库,比如boost的thread等等。 _beginthread和CreateThread的区别:_beginthread内部调用了CreateThread. 如果你的程序只调用 Win32 API/SDK ,就放心用 CreateThread,如果要用到C++运行时库,那么就要使用_beginthreadex,因为C++运行库有一些函数里面使用了全局变量,beginthreadex 为这些全局变量做了处理,使得每个线程都有一份独立的“全局”量,在这种情况下使用CreateThread的话就会出现不安全的问题 ●进程有哪几种状态,状态转换图,及导致转换的事件 ●死锁 概念: 进程间进行通信或相互竞争系统资源而产生的永久阻塞,若无外力作用将永远处在死锁状态。 产生原因: 系统资源不足;进程运行推进顺序与速度不同也可能导致死锁;资源分配不当; 产生死锁四个必要条件: 互斥条件:就是一个资源每次只能被一个进程使用。请求与保持条件:一个进程在请求其它资源而阻塞时,但是它对自己已获得的资源又保持不放。不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。 预防死锁和避免死锁的方法: 在系统设计、进程调度方面注意不让产生死锁的四个必要条件成立,确定资源的合理分配算法,避免进程永远占用系统资源,对资源分配要进行合理的规划。 ●多线程中栈与堆是公有的还是私有的 因为线程是共享进程的资源的,所以栈是私有的,堆是公有的。 ●线程池的概念 线程池就是一堆已经创建好的线程,最大数目一定,然后初始后都处于空闲状态,当有新任务进来时就从线程池中取出空闲线程处理任务,任务完成之后又重新放回去,当线程池中的所有线程都在任务时,只能等待有线程结束任务才能继续执行。
2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/40312.html