1.进程和线程的区别
进程——资源分配的最小单位,
线程——程序执行的最小单位。
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
协程与线程主要区别是它将不再被内核调度,而是交给了程序自己而线程是将自己交给内核调度。
1.实际意义的区别
(1)一个程序至少有一个进程,一个进程至少有一个线程。线程(Thread)是进程的一个实体,是CPU调度和分派的基本单位;
(2)进程拥有独立的内存单元,而多个线程共享内存。从而线程效率更高;
(3)进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮;
(4)进程切换时,耗费资源较大,效率要差一些;
(5)进程是系统资源分配的基本单位,线程是调度的基本单位。
2.比较进程线程的优点
(1)易于调度。
(2)提高并发性。通过线程可方便有效地实现并发性。进程可创建多个线程来执行同一程序的不同部分。
(3)开销少。创建线程比创建进程要快,所需开销很少。
(4)利于充分发挥多处理器的功能。
3.相比进程线程的缺点
(1)线程之间的同步和加锁控制比较麻烦
(2)一个线程的崩溃影响到整个程序的稳定性
(3)线程多了之后,线程本身的调度也是一个麻烦事儿,需要消耗较多的CPU
4.通讯的区别
(1)每个进程有自己的地址空间。两个进程中的地址即使值相同,实际指向的位置也不同。进程间通信一般通过操作系统的公共区进行。
同一进程中的线程因属同一地址空间,可直接通信。
(2)只有进程间需要通信,同一进程的线程share地址空间,没有通信的必要,但要做好同步/互斥mutex,保护共享的全局变量。线程拥有自己的栈。同步/互斥是原语primitives. 而进程间通信无论是信号,管道pipe还是共享内存都是由操作系统保证的,是系统调用.
(3)线程间通信:由于多线程共享地址空间和数据空间,所以多个线程间的通信是一个线程的数据可以直接提供给其他线程使用,而不必通过操作系统(也就是内核的调度)。进程间的通信则不同,它的数据空间的独立性决定了它的通信相对比较复杂,需要通过操作系统。以前进程间的通信只能是单机版的,现在操作系统都继承了基于套接字(socket)的进程间的通信机制。这样进程间的通信就不局限于单台计算机了,实现了网络通信。
5.切换和调度
线程上下文切换比进程上下文切换要快得多,在多线程程序下,进程不是一个可执行的实体
2.协程
协程是用户模式下的轻量级线程,最准确的名字应该叫用户空间线程(User Space Thread)
操作系统内核对协程一无所知,协程的调度完全有应用程序来控制,操作系统不管这部分的调度;
一个线程可以包含一个或多个协程,协程拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下文和栈保存起来,在切换回来时恢复先前保运的寄存上下文和栈。
协程的优势如下:
- 节省内存,每个线程需要分配一段栈内存,以及内核里的一些资源
- 节省分配线程的开销(创建和销毁线程要各做一次 syscall)
- 节省大量线程切换带来的开销
- 与 NIO 配合实现非阻塞的编程,提高系统的吞吐
3.进程间的通讯方式
(1)管道:半双工;数据只能单向流动,只能用于具有亲缘关系的进程之间,即用于父子、兄弟之间。
(2)命名管道(FIFO):半双工,允许无亲缘关系的进程
(3)消息队列:消息链表存于内核,每个消息队列由消息队列标识符标识;于管道不同的是,消息队列存放在内核中,只有在内核重启时才能删除一个消息队列;消息队列的大小受限制。
(4)信号量(semophore):信号量是一个计数器,可以用来控制多个进程对于共享资源的访问。作为一种锁机制,防止某进程正在访问共享资源师,其他进程也访问该资源,常用来处理临界资源的访问同步问题。
临界资源:为某一时刻只能由一个进程或线程操作的资源。
(5)共享内存:就是映射一段能被其他进程所访问的内存,这段内存由一个进程创建,但可以多个进程同时访问,可以说是最有用的进程间通信方式,也是最快的IPC形式。常与其他通讯机制(信号量)配合使用。
(6)套接字:也可用于不同机器之间。
(7)信号(Signal):比较复杂,用于通知接收进程某个事件已经发生
4.进程的状态
1.创建状态:程由创建而产生。创建进程是一个非常复杂的过程,一般需要通过多个步骤才能完成:如首先由进程申请一个空白的进程控制块(PCB),并向PCB中填写用于控制和管理进程的信息;然后为该进程分配运行时所必须的资源;最后,把该进程转入就绪状态并插入到就绪队列中。
2.就绪状态:这是指进程已经准备好运行的状态,即进程已分配到除CPU以外所有的必要资源后,只要再获得CPU,便可立即执行。如果系统中有许多处于就绪状态的进程,通常将它们按照一定的策略排成一个队列,该队列称为就绪队列。有执行资格,没有执行权的进程。
3.运行状态:这里指进程已经获取CPU,其进程处于正在执行的状态。对任何一个时刻而言,在单处理机的系统中,只有一个进程处于执行状态而在多处理机系统中,有多个进程处于执行状态。既有执行资格,又有执行权的进程。
4.阻塞状态:这里是指正在执行的进程由于发生某事件(如I/O请求、申请缓冲区失败等)暂时无法继续执行的状态,即进程执行受到阻塞。此时引起进程调度,操作系统把处理机分配给另外一个就绪的进程,而让受阻的进程处于暂停的状态,一般将这个暂停状态称为阻塞状态
5.终止状态:进程的终止也要通过两个步骤:首先,是等待操作系统进行善后处理,最后将其PCB清零,并将PCB空间返还给系统。当一个进程到达了自然结束点,或是出现了无法克服的错误,或是被操作系统所终结,或是被其他有终止权的进程所终结,它将进入终止状态。进入终止态的进程以后不能在再执行,但是操作系统中任然保留了一个记录,其中保存状态码和一些计时统计数据,供其他进程进行收集。一旦其他进程完成了对其信息的提取之后,操作系统将删除其进程,即将其PCB清零,并将该空白的PCB返回给系统。
5.线程共享的和独有的内容
线程独有的内容:
线程上下文 包括:线程ID 、栈、栈指针、PC(程序计数器)、通用目的寄存器、条件码、错误返回码、线程的信号屏蔽码、线程的优先级
线程共享的内容:
线程共享的环境包括:进程代码段、进程的公有数据、进程打开的文件描述符、信号的处理器、进程的当前目录、进程用户 ID 与进程组 ID 等
6.线程同步的方式
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
锁机制:包括互斥锁、条件变量、读写锁
(1)临界区:当多个线程访问一个独占性共享资源时,可以使用临界区对象。拥有临界区的线程可以访问被保护起来的资源或代码段,其他线程若想访问,则被挂起,直到拥有临界区的线程放弃临界区为止。
(2)互斥量(Mutex):提供了以排他方式防止数据结构被并发修改的方法,互斥对象和临界区对象非常相似,只是其允许在进程间使用,也可在线程间使用,而临界区只限制与同一进程的各个线程之间使用。
(3)条件变量:以原子的方式阻塞进程,直到某个特定条件为真为止,一个线程被挂起,直到某件事件发生。
条件变量始终与互斥锁一起使用。
(4)信号量(semaphore):当需要一个计数器来限制可以使用某共享资源的线程数目时,可以使用“信号量”对象。CSemaphore类对象保存了对当前访问某一个指定资源的线程的计数值,该计数值是当前还可以使用该资源的线程数目。如果这个计数达到了零,则所有对这个CSemaphore类对象所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或计数值不为零为止。mutex是semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。但是,因为mutex较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。
(5)信号:类似进程间的信号处理
(6)事件:允许一个线程在处理完一个任务后,主动唤醒另外一个线程执行任务。
(7)套接字:可用于两个机器之间
7.多线程的锁机制
互斥量(Mutex) 互斥量是实现最简单的锁类型,因此有一些教科书一般以互斥量为例对锁原语进行描述。互斥量的释放并不仅仅依赖于释放操作,还可以引入一个定时器属性。如果在释放操作执行前发生定时器超时,则互斥量也会释放代码块或共享存储区供其他线程访问。当有异常发生时,可使用try-finally语句来确保互斥量被释放。定时器状态或try-finally语句的使用可以避免产生死锁。
递归锁(Recursive Lock) 递归锁是指可以被当前持有该锁的线程重复获取,而不会导致该线程产生死锁的锁类型。对递归锁而言,只有在当前持有线程的获取锁操作都有一个释放操作与之对应时,其他线程才可以获取该锁。因此,在使用递归锁时,必须要用足够的释放锁操作来平衡获取锁操作,实现这一目标的最佳方式是在单入口单出口代码块的两头一一对应地使用获取、释放操作,做法和在普通锁中一样。递归锁在递归函数中最有用。但是,总的来说,递归锁比非递归锁速度要慢。需要注意的是:调用线程获得几次递归锁必须释放几次递归锁。
读写锁(Read-Write lock) 读写锁又称为共享独占锁(shared-exclusive lock)、多读单写锁(multiple-read/single-write lock)或者非互斥信号量(non-mutual exclusion semaphore)。读写锁允许多个线程同时进行读访问,但是在某一时刻却最多只能由一个线程执行写操作。对于多个线程需要同时读共享数据却并不一定进行写操作的应用来说,读写锁是一种高效的同步机制。对于较长的共享数据,只为其设置一个读写锁会导致较长的访问时间,最好将其划分为多个小段并设置多个读写锁以进行同步。
旋转锁(Spin Lock) 旋转锁是一种非阻塞锁,由某个线程独占。采用旋转锁时,等待线程并不静态地阻塞在同步点,而是必须“旋转”,不断尝试直到最终获得该锁。旋转锁多用于多处理器系统中。这是因为,如果在单核处理器中采用旋转锁,当一个线程正在“旋转”时,将没有执行资源可供另一释放锁的线程使用。旋转锁适合于任何锁持有时间少于将一个线程阻塞和唤醒所需时间的场合。线程控制的变更,包括线程上下文的切换和线程数据结构的更新,可能比旋转锁需要更多的指令周期。旋转锁的持有时间应该限制在线程上下文切换时间的50%到100%之间(Kleiman,1996年)。在线程调用其他子系统时,线程不应持有旋转锁。对旋转锁的不当使用可能会导致线程饿死,因此需谨慎使用这种锁机制。旋转锁导致的饿死问题可使用排队技术来解决,即每个等待线程按照先进先出的顺序或者队列结构在一个独立的局部标识上进行旋转
8.进程间通讯方式的区别
共享内存和消息队列,FIFO,管道传递消息的区别:
后者,消息队列,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的时候,就阻塞在哪里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这个资源。比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁和信号量会同时使用的”也就是说,信号量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进行操作。在有些情况下两者可以互换。
使用互斥锁来实现线程间数据的共享和通信,互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步
作用域
信号量: 进程间或线程间(linux仅线程间)
互斥锁: 线程间
上锁时
信号量: 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait阻塞,直到sem_post释放后value值加一
互斥锁: 只要被锁住,其他任何线程都不可以访问被保护的资源,成功后否则就阻塞
以下是信号灯(量)的一些概念:
信号灯与互斥锁和条件变量的主要不同在于”灯”的概念,灯亮则意味着资源可用,灯灭则意味着不可用。如果说后两中同步方式侧重于”等待”操作,即资源不可用的话,信号灯机制则侧重于点灯,即告知资源可用;没有等待线程的解锁或激发条件都是没有意义的,而没有等待灯亮的线程的点灯操作则有效,且能保持灯亮状态。当然,这样的操作原语也意味着更多的开销。
12.一个进程(Process)最多可以生成多少个线程(Thread)
默认情况下,一个线程的栈要预留1M的内存空间,而一个进程中可用的内存空间只有2G,所以理论上一个进程中最多可以开2048个线程,但是内存当然不可能完全拿来作线程的栈,所以实际数目要比这个值要小。
13.进程的并发与并行
并发:在单核 CPU 系统中,系统调度在某一时刻只能让一个进程运行,虽然这种调度机制有多种形式(大多数是时间片轮巡为主),但无论如何,要通过不断切换需要运行的进程让其运行的方式叫并发
并行:在多核 CPU 系统中,可以让两个以上的进程同时运行在不同的物理核心上,这种运行的方式就是并行
区别
并发在微观上不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行,因为 CPU 计算速度很快,从宏观上看,好像这些进程都 在同一个时间点执行
并行是真正的细粒度上的同时进行:既同一时间点上同时运行着多个进程
全部评论
(4) 回帖