进程——资源分配的最小单位,
线程——程序执行的最小单位。
协程是用户态的轻量级线程,协程的调度完全由用户控制。 协程有自己的寄存器上下文和堆栈。 当协程调度切换时,寄存器上下文和堆栈被保存在其他地方。 当切换回来时,之前保存的寄存器上下文和堆栈将被恢复。 直接操作栈基本上没有内核切换的开销,并且可以访问全局变量而无需加锁。 ,所以上下文切换非常快。
协程和线程的主要区别在于,它不再由内核调度,而是交给程序本身,而线程则将自己交给内核调度。
1、实际意义的差异
(1)一个程序至少有一个进程,一个进程至少有一个线程。 线程是进程的实体,是CPU调度和分派的基本单位;
(2)进程有独立的内存单元,多个线程共享内存。 因而线程效率更高;
(3)进程具有独立的地址空间。 一个进程崩溃后,不会影响其他处于保护模式的进程。 线程没有单独的地址空间。 一个线程的死亡意味着整个进程的死亡,因此多进程程序比多线程程序更健壮;
(4)进程切换时,消耗资源较多,效率较低;
(5)进程是系统资源分配的基本单位,线程是调度的基本单位。
2.比较进程线程的优点
(1)易于安排。
(2)提高并发性。 通过线程可以轻松高效地实现并发。 一个进程可以创建多个线程来执行同一程序的不同部分。
(3) 更少的开销。 创建线程比创建进程更快,并且需要很少的开销。
(4)有利于充分发挥多处理器的功能。
3. 与进程线程相比的缺点
(1)线程间的同步和锁控制比较麻烦
(2)某个线程的崩溃影响整个程序的稳定性
(3)线程过多后,线程本身的调度也是一件麻烦的事情,需要更多的CPU。
4、通讯方式的区别
(1)每个进程都有自己的地址空间。 即使两个进程中的地址具有相同的值,它们实际上也指向不同的位置。 进程间通信一般通过操作系统的公共区域进行。
同一进程中的线程属于同一地址空间,可以直接通信。
(2) 只需要进程之间的通信。 同一进程中的线程共享地址空间。 不需要通信,但是必须要做同步/互斥互斥,以保护共享的全局变量。 线程有自己的堆栈。 同步/互斥是原语。 进程间的通信,无论是信号、管道还是共享内存,都是由操作系统保证的,都是系统调用。
(3)线程间通信:由于多线程共享地址空间和数据空间,多线程之间的通信意味着一个线程的数据可以直接提供给其他线程使用,而无需经过操作系统(即,内核调度)。 进程之间的通信是不同的。 其数据空间的独立性决定了其通信相对复杂,需要经过操作系统。 以前进程间通信只能是单机版本。 现在操作系统已经继承了基于socket的进程间通信机制。 这样进程间通信就不再局限于单台计算机,实现了网络通信。
5. 切换与调度
线程上下文切换比进程上下文切换快得多。 在多线程程序中,进程不是可执行实体。
2.协程
协程是用户模式下的轻量级线程。 最准确的名字应该叫User Space Thread。
操作系统内核对协程一无所知。 协程的调度完全由应用程序控制,操作系统不关心这部分调度;
一个线程可以包含一个或多个协程。 协程有自己的寄存器上下文和堆栈。 当协程调度切换时,会保存注册上下文和堆栈,切换回来时会恢复之前保存的注册上下文和堆栈。
协程的优点如下:
3.进程间通信方式
(1)管道:半双工; 数据只能朝一个方向流动,只能在有关系的进程之间使用,即父子之间、兄弟之间。
(2)命名管道(FIFO):半双工,允许不相关的进程
(3)消息队列:内核中存储消息链表,每个消息队列由消息队列标识符来标识; 与管道不同,消息队列存储在内核中,只有在内核重启时才能删除消息队列; 消息队列的大小是有限的。
(4)信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。 作为一种锁定机制,它可以防止进程访问共享资源以及其他进程也访问该资源。 常用于处理关键资源的访问同步问题。
关键资源:一次只能由一个进程或线程操作的资源。
(5)共享内存:是映射一段可以被其他进程访问的内存。 该内存由一个进程创建unity 协程和线程区别,但可以同时被多个进程访问。 可以说是最有用的进程间通信方式,也是最快的IPC。 形式。 通常与其他通信机制(信号量)结合使用。
(6) Sockets:也可以在不同机器之间使用。
(7)信号:比较复杂,用于通知接收进程有事件发生
4. 进程状态
1、创建状态:流程由创建产生。 创建进程是一个非常复杂的过程,一般需要多个步骤才能完成:首先,进程申请一个空白的进程控制块(PCB),并在PCB中填充用于控制和管理进程的信息; 然后为进程分配运行所需的资源; 最后,进程转入就绪状态,并插入就绪队列。
2.就绪状态:这是指进程准备运行的状态。 即进程分配完除CPU之外的所有必要资源后,只要再次获得CPU就可以立即执行。 如果系统中有很多处于就绪状态的进程,它们通常按照一定的策略排列在一个队列中,称为就绪队列。 具有执行资格但不具有执行权的进程。
3、运行状态:表示进程已获得CPU,进程处于执行状态。 任何时候,在单处理器系统中,只有一个进程在执行,而在多处理器系统中,则有多个进程在执行。 既具有执行资格又具有执行权的进程。
4、阻塞状态:这是指正在执行的进程由于某个事件(如I/O请求、申请缓冲区失败等)的发生而暂时无法继续执行的状态,即进程执行被阻塞。此时,引起进程调度,操作系统将处理器分配给另一个就绪进程,使阻塞进程处于挂起状态。 这种暂停状态通常称为阻塞状态。
5、终止状态:进程的终止也需要两个步骤:首先等待操作系统处理善后,最后清除其PCB,将PCB空间归还给系统。 当一个进程到达其自然结束点,或者发生不可克服的错误,或者被操作系统终止,或者被其他有权终止的进程终止时,它将进入终止状态。 进入终止状态的进程无法再次执行,但操作系统中仍然保留一条记录,该记录保存状态代码和一些时序统计信息以供其他进程收集。 一旦其他进程完成信息提取,操作系统将删除它们的进程贴图笔刷,清除它们的 PCB,并将空白 PCB 返回到系统。
5. 线程共享且独特的内容
线程独特的内容:
线程上下文包括:线程ID、堆栈、堆栈指针、PC(程序计数器)、通用寄存器、条件码、错误返回码、线程信号掩码、线程优先级
该线程分享的内容:
线程共享的环境包括:进程代码段、进程的公共数据、进程打开的文件描述符、信号处理程序、进程的当前目录、进程用户ID和进程组ID等。
6.线程同步方法
线程之间通信的目的主要是为了线程同步,因此线程没有像进程内通信那样进行数据交换的通信机制。
锁机制:包括互斥锁、条件变量、读写锁
(1)临界区:当多个线程访问独占共享资源时unity 协程和线程区别,可以使用临界区对象。 拥有临界区的线程可以访问受保护的资源或代码段。 如果其他线程想要访问,它们将被挂起,直到拥有该临界区的线程放弃该临界区。
(2)互斥体(Mutex):提供一种排他的方式来防止数据结构被并发修改。 互斥对象与临界区对象非常相似,只不过它们允许在进程和线程之间使用。 临界区只能在同一进程的线程之间使用。
(3)条件变量:以原子方式阻塞进程,直到特定条件为真,线程被挂起直到事件发生。
条件变量始终与互斥锁一起使用。
(4)信号量:当需要一个计数器来限制可以使用共享资源的线程数量时,可以使用“信号量”对象。 CSemaphore类对象保存了当前访问指定资源的线程的计数值。 计数值是当前可以使用该资源的线程数。 如果此计数达到零,则对此 CSemaphore 类对象控制的资源的所有访问尝试都会放入队列中,并等待直到超时或计数值不为零。 互斥锁是信号量的一种特殊情况(当 n=1 时)。 也就是说,后者完全可以取代前者。 但由于互斥量相对简单且高效,因此在必须保证资源独占性的情况下仍然采用这种设计。
(5)信号:类似于进程间的信号处理
(6)事件:允许一个线程在处理完一项任务后,主动唤醒另一线程来执行任务。
(7) Socket:可以在两台机器之间使用
7.多线程锁机制
互斥量(Mutex) 互斥量是实现起来最简单的锁类型,因此一些教科书一般以互斥量为例来描述锁原语。 互斥量的释放不仅仅依赖于释放操作,还可以引入定时器属性。 如果在执行释放操作之前定时器超时,则互斥锁还会释放代码块或共享存储以供其他线程访问。 当发生异常时,可以使用try-finally语句来确保互斥体被释放。 使用计时器状态或 try-finally 语句可以避免死锁。
递归锁(Recursive Lock) 递归锁是指当前持有锁的线程可以重复获取而不导致线程死锁的一类锁。 对于递归锁来说,只有当当前持有线程的锁获取操作有对应的释放操作时,其他线程才能获取锁。 因此,在使用递归锁时,必须使用足够的释放锁操作来平衡锁获取操作。 实现这一目标的最佳方式是在单进单出代码块的两端一对一地使用获取和释放操作。 该过程与普通锁相同。 递归锁在递归函数中最有用。 然而,一般来说,递归锁比非递归锁慢。 需要注意的是,调用线程在获取递归锁后必须多次释放递归锁。
读写锁(Read-Write lock) 读写锁也称为共享独占锁(shared-exclusive lock)、多读/单写锁(multiple-read/single write lock)或非互斥锁信号量(非互斥信号量)。 读写锁允许多个线程同时执行读访问,但同一时间最多只有一个线程可以执行写操作。 对于多个线程需要同时读取共享数据但不一定进行写操作的应用程序来说,读写锁是一种高效的同步机制。 对于长的共享数据,只为其设置一个读写锁会导致访问时间更长。 最好分成多个小段,设置多个读写锁进行同步。
自旋锁 自旋锁是一种非阻塞锁,由线程独占。 使用自旋锁时,等待线程不会静态地阻塞在同步点,而是必须“自旋”并不断尝试,直到最终获得锁。 旋转锁主要用于多处理器系统中。 这是因为,如果在单核处理器中使用自旋锁,当一个线程“自旋”时,将没有可用的执行资源供另一个线程释放锁。 自旋锁适用于任何锁持有时间少于阻塞和唤醒线程所需时间的情况。 线程控制的变化,包括线程上下文切换和线程数据结构的更新,可能需要比自旋锁更多的指令周期。 自旋锁持有时间应限制在线程上下文切换时间的 50% 到 100% 之间(Kleiman,1996)。 线程在调用其他子系统时不应持有自旋锁。 自旋锁使用不当可能会导致线程饥饿,所以要谨慎使用这种锁机制。自旋锁引起的饥饿问题可以使用排队技术来解决,即每个等待线程在一个先进先出的情况下轮流在一个独立的本地标识符上、先出顺序或队列结构。
8.进程间通信方式的差异
共享内存与消息队列、FIFO、管道消息传递的区别:
后者,消息队列、先进先出、管道消息传递方式一般是
1:服务器获取输入
2:消息队列通过管道写入数据,通常需要从进程复制到内核。
3:客户端从内核复制到进程
4:然后从进程复制到输出文件
上述过程通常需要4份才能完成文件的传输。
共享内存只需要
1:从输入文件到共享内存区域
2:从共享内存区输出到文件
上述过程不涉及复制内核,因此花费的时间较少。
9.多线程编程(线程池),如何确定线程数
首先判断应用是CPU密集型(如分词、加密等)还是IO耗时(网络、文件操作等)
CPU密集型:最佳线程数等于CPU核心数或略小于CPU核心数。 CPU核心数=线程数。 一般我们会设置Cpu核数+1,防止因为其他因素导致线程阻塞。
耗时IO类型:最佳线程数一般比CPU核数大很多倍。 。 通常,IO设备延迟除以CPU处理延迟得到倍数。 我的经验值是20-50倍*CPU核心数。
多核CPU的最佳线程数 = CPU核心数 * [ 1 + (I/O时间消耗/Cpu时间消耗)
最佳线程数还与机器配置(内存、磁盘速度)有关。 如果CPU、内存、磁盘任何一个达到峰值,就需要适当减少线程数。
默认情况下,为线程的堆栈保留1M的内存空间,而进程中的可用内存空间只有2G。 因此,理论上一个进程中最多可以开启2048个线程,但当然不可能将所有内存都用于线程。 堆栈中,所以实际数量小于该值。
10. 使用多线程的原因
1).防止界面卡顿。
改善用户体验
对于单核CPU和客户端软件,采用多线程,主要是创建多线程,在后台执行一些计算,而不影响用户交互。 (用户界面和其他计算并行执行)提高用户的操作性能!
2)。 使用线程进行耗时操作(io、网络io等),以提高cpu使用率。
I/O操作不仅包括直接的文件和网络读写,还包括数据库操作、Web Service、HttpRequest、.net Remoting等跨进程调用。
如果不使用多线程,你会发现CPU使用率非常空闲。
3).在多CPU(核心)下,使用线程来提高CPU利用率
使多CPU系统更加高效
操作系统会保证当线程数不大于CPU数时,不同的线程运行在不同的CPU上。
如果不使用多线程,你会发现只有一个CPU很忙,其他CPU都空闲。
4). 不适用于多线程情况
A。 您的代码是 CPU 密集型的,并且在单核 CPU 上运行。
b. 在单核CPU上,线程的使用(滥用)会给系统带来额外的上下文切换负担。 并且线程之间共享变量可能会导致死锁。
C。 当需要进行I/O操作时,往往使用异步操作比使用线程+同步I/O操作更合适。
对于耗时IO类型,一个简单的算法:最优线程数==单线程黄色时间块长度(空闲)/绿色时间块长度(繁忙)*cpu核数
11、互斥锁条件变量信号量的区别
信号量用于多线程和多任务同步。 当一个线程完成某个动作时,它通过信号量告诉其他线程,其他线程然后执行某些动作(当每个人都在semtake时,他们会阻塞在那里)。 互斥锁用于多线程、多任务的互斥。 如果一个线程占用了某一资源,其他线程就无法访问它。 直到该线程被解锁,其他线程才能开始使用该资源。 例如,访问全局变量有时需要在操作完成后加锁和解锁。 有时会同时使用锁和信号量。 换句话说,信号量不一定是锁定某个资源,而是一个进程概念。 例如:有两个线程A和B,线程B要等待线程A完成。 完成特定任务后,您可以执行以下步骤。 这个任务不一定涉及锁定某个资源,也可以进行一些计算或者数据处理。 线程互斥体是“锁定某个资源”的概念,在锁定期间,其他线程不能对受保护的数据进行操作。 在某些情况下,两者可以互换。
互斥锁用于实现线程间的数据共享和通信。 互斥锁的一个明显缺点是它们只有两种状态:锁定和非锁定。 条件变量通过允许线程阻塞并等待另一个线程发送信号来弥补互斥锁的缺点。 它们经常与互斥锁一起使用。 使用时游戏运营,条件变量用于阻塞线程。 当条件不满足时,线程往往会解锁相应的互斥锁并等待条件改变。 一旦某个其他线程改变了条件变量,它就会通知相应的条件变量去唤醒一个或多个被该条件变量阻塞的线程。 这些线程会重新锁定互斥体并重新测试条件是否满足。一般来说,条件变量用于线路载波之间的同步
范围
信号量:进程间或线程间(linux 仅限线程间)
互斥锁:线程之间
锁定时
信号量:只要信号量的值大于0,其他线程就可以sem_wait成功。 成功后,信号量的值减一。如果该值不大于0,sem_wait会阻塞,直到sem_post释放后该值加一。
互斥锁:只要被锁定,其他线程就不能访问受保护的资源。 否则成功后会被屏蔽。
以下是信号灯(数量)的一些概念:
信号量、互斥锁和条件变量之间的主要区别在于“光”的概念。 灯亮时表示资源可用,灯灭时表示资源不可用。 如果后两种同步方式侧重于“等待”操作,即资源不可用,则信号量机制侧重于点亮灯,即通知资源资源可用; 不等待线程解锁或激活条件是没有意义的,不等待灯亮起线程的照明操作是有效的,灯可以保持亮着。 当然,这样的操作原语也意味着更多的开销。
12、一个进程可以生成的最大线程数(Threads)
默认情况下,为线程的堆栈保留1M的内存空间,而进程中的可用内存空间只有2G。 因此,理论上一个进程中最多可以开启2048个线程,但当然不可能将所有内存都用于线程。 堆栈中,所以实际数量小于该值。
13. 进程并发性和并行性
并发性:在单核CPU系统中,系统调度在某一时刻只能允许一个进程运行。 虽然这种调度机制有多种形式(多为时间片轮询),但无论如何,它必须不断地切换需要运行的进程并让其运行的方法称为并发。
并行性:在多核CPU系统中,两个或多个进程可以同时在不同的物理核心上运行。 这种运行方式就是并行。
区别
并发从微观上看并不是同时执行的。 它只是将时间分成若干段,以便多个进程可以快速交替执行。 因为CPU的计算速度非常快,从宏观上看,似乎这些进程都是在同一个时间点执行的。
并行是真正的细粒度同时执行:多个进程在同一时间点同时运行。
#学习路径#