协程
概念
协程是微线程,在子程序内部执行,可在子程序内部中断,转而执行别的子程序,在适当的时候再返回来接着执行。
优势
- 协程调用跟切换比线程效率高:协程执行效率极高。协程不需要多线程的锁机制,可以不加锁的访问全局变量,所以上下文的切换非常快。
- 协程占用内存少:执行协程只需要极少的栈内存(大概是4~5KB),而默认情况下,线程栈的大小为1MB。
- 切换开销更少:协程直接操作栈基本没有内核切换的开销,所以切换开销比线程少。
锁
悲观锁
悲锁并不是某一个锁,是一个锁类型,无论是否并发竞争资源,都会锁住资源,并等待资源释放下一个线程才能获取到锁。 这明显很悲观,所以就叫悲观锁。这明显可以归纳为一种策略,只要符合这种策略的锁的具体实现,都是悲观锁的范畴。
传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁
与悲观锁相对的,乐观锁也是一个锁类型。当线程开始竞争资源时,不是立马给资源上锁,而是进行一些前后值比对,以此来操作资源。
乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。(CAS)
CAS
- 概念
- 是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。
- 具体业务
- 高并发环境下,对同一个数据的并发读(两边都读出余额是100)与并发写(一个写回28,一个写回38)导致的数据一致性问题。
- 解决方案是在set写回的时候,加上初始状态的条件compare,只有初始状态不变时,才允许set写回成功,这是一种常见的降低读写锁冲突,保证数据一致性的方法。
自旋锁
概念
自旋锁是一种基础的同步原语,用于保障对共享数据的互斥访问。与互斥锁的相比,在获取锁失败的时候不会使得线程阻塞而是一直自旋尝试获取锁。当线程等待自旋锁的时候,CPU不能做其他事情,而是一直处于轮询忙等的状态。
实际上许多其他类型的锁在底层使用了自旋锁实现,例如多数互斥锁在试图获取锁的时候会先自旋一小段时间,然后才会休眠。
特点
- 用于临界区互斥
- 在任何时刻最多只能有一个执行单元获得锁
- 要求持有锁的处理器所占用的时间尽可能短
- 等待锁的线程进入忙循环
问题
如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗CPU。使用不当会造成CPU使用率极高。
无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在"线程饥饿"问题。
优点
自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快
非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。(线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)
互斥锁
概念
对共享数据进行锁定,保证同一时刻只能有一个线程去操作。
作用
互斥锁的作用就是保证同一时刻只能有一个线程去操作共享数据,保证共享数据不会出现错误问题
优点
确保某段关键代码只能由一个线程从头到尾完整地去执行
缺点
使用互斥锁会影响代码的执行效率,多任务改成了单任务执行
互斥锁如果没有使用好容易出现死锁的情况
自旋锁和互斥锁的区别:
- 自旋锁与互斥锁都是为了实现保护资源共享的机制。
- 无论是自旋锁还是互斥锁,在任意时刻,都最多只能有一个保持者。
- 获取互斥锁的线程,如果锁已经被占用,则该线程将进入睡眠状态;获取自旋锁的线程则不会睡眠,而是一直循环等待锁释放。
公平锁
多个线程竞争同一把锁,如果依照先来先得的原则,那么就是一把公平锁。
非公平锁
多个线程竞争锁资源,抢占锁的所有权。
共享锁
多个线程可以共享这个锁的拥有权。一般用于数据的读操作,防止数据被写修改。
如果一个线程已经获取了共享锁,则其他任何线程都无法获取互斥锁,但是可以获取共享锁
如果一个线程已经获取了互斥锁,则其他线程都无法获取该锁。
死锁
概念
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
条件
- 互斥条件
互斥条件。即某个资源在一段时间内只能由一个进程占有,不能同时被两个或两个以上的进程占有。
如独木桥就是一种独占资源,两方的人不能同时过桥。
- 不剥夺条件
进程所获得的资源在未使用完毕之前,资源申请者不能强行地从资源占有者手中夺取资源,而只能由该资源的占有者进程自行释放。
如过独木桥的人不能强迫对方后退,也不能非法地将对方推下桥,必须是桥上的人自己过桥后空出桥面(即主动释放占有资源),对方的人才能过桥。
- 请求和保持条件
进程至少已经占有一个资源,但又申请新的资源;由于该资源已被另外进程占有,此时该进程阻塞;但是,它在等待新资源之时,仍继续占用已占有的资源。
还以过独木桥为例,甲乙两人在桥上相遇。甲走过一段桥面(即占有了一些资源),还需要走其余的桥面(申请新的资源),但那部分桥面被乙占有(乙走过一段桥面)。甲过不去,前进不能,又不后退;乙也处于同样的状况。
- 循环等待条件
循环等待条件。存在一个进程等待序列{P1,P2,...,Pn},其中P1等待P2所占有的某一资源,P2等待P3所占有的某一源,...,而Pn等待P1所占有的的某一资源,形成一个进程循环等待环。
甲等待乙占有的桥面,而乙又等待甲占有的桥面,从而彼此循环等待。
I/O多路复用
概念
IO多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;没有文件句柄就绪时会阻塞应用程序,交出cpu。
多路是指网络连接,复用指的是同一个线程。
实现方式
select
概念
时间复杂度O(n),它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。
特点
- select具有O(n)的无差别轮询复杂度,
- 同时处理的流越多,无差别轮询时间就越长。
- select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。
缺点
select的一 个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024
poll
概念
时间复杂度O(n),poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,
特点
但是它没有最大连接数的限制,原因是它是基于链表来存储的。
poll fd结构包含了要监视的event和发生的event,不再使用select"参数-值"传递的方式。
缺点
虽然没有数量限制,但是数量过大的话性能会下降
epoll
概念
时间复杂度O(1),epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以说epoll实际上是事件驱动(每个事件关联上fd) 的,此时对这些流的操作都是有意义的。
优点
没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口)。
效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;即Epoll最大的优点就在于它只管你"活跃"的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。
区别:
- 一个进程能打开的最大连接数
2. fd剧增后带来的效率问题(fd:操作对象的一个整数)
3. 消息传递方式
中断
定义
中断是指CPU在执行当前程序时系统出现了某种状况,使得CPU必须停止当前程序,而去执行另一段程序来处理的出现的紧急事务,处理结束后CPU再返回到原先暂停的程序继续执行,这个过程就称为中断。
作用
使得计算机系统具备应对对处理突发事件的能力,使其能及时响应紧急事件。
提高处理器效率,如果没有中断系统,CPU就只能按照原来的程序编写的先后顺序,对各个外设进行查询和处理,即轮询工作方式,轮询方法貌似公平,但实际工作效率却很低。
分类
内部中断(CPU本身在执行程序的过程中产生的)
内部中断分为两种
异常
定义
异常是另外一种内部中断,是指令执行期间CPU内部产生的错误引起的;
分类
- 故障
故障可以修正,如果修复成功,将返回到当前正在执行的指令,重新执行。否则将终止故障程序;
- 陷阱
陷阱会导致程序停止;
- 终止
由不可恢复的知名错误造成,处理器会终止应用程序。
软中断
定义
是由软件产生的中断。
外部中断(由CPU外部产生)(硬中断)
定义
外部中断是系统外部设备引发的程序中断,一般分为可屏蔽中断和不可屏蔽中断。
作用
主要是用来通知操作系统外设状态的变化。
分类
- 可屏蔽中断
可屏蔽中断是由由中断能力的外部设备发出,例如I/O设备发出的所有中断请求都属于可屏蔽中断。
- 不可屏蔽中断
内部不可屏蔽中断是通过软件调用的中断以及由执行指令过程中产生的"异常"。包括溢出中断、除法出错中断等。
软中断与硬中断:
当中断发生的时候,硬中断处理那些短时间就可以完成的工作,而将那些处理事件比较长的工作,放到中断之后来完成,也就是软中断(softirq)来完成。
中断处理
步骤
上半部分是比较紧急的事情,需要被立即执行,并且不可被中断;
下半部分则一般是不太紧急又耗时的事情。
多个中断
处理方式
第一种方式是在处理中断时禁止在此发生中断
第二种方式是考虑中断的优先级,允许高优先级的中断程序打断第优先级中断的运行,处理完高优先级中断后再返回处理原本的中断。
也就是所谓的中断嵌套