同步、异步、阻塞、非阻塞的概念
同步异步,阻塞非阻塞是两个不同层面的问题,一个是operation层面,一个是kernal层面。同步异步最大的区别在于是否需要底层的响应再执行。阻塞非阻塞最大的区别在于是否立即给出响应。
同步:当一个同步调用发出后,调用者要一直等待返回结果。通知后,才能进行后续的执行。
异步:当一个异步过程调用发出后,调用者不能立刻得到返回结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。
阻塞:是指调用结果返回前,当前线程会被挂起,即阻塞。
非阻塞:是指即使调用结果没返回,也不会阻塞当前线程。
形象比喻:
小Q去钓鱼,抛完线后就傻傻的看着有没有动静,有则拉杆(同步阻塞);
小Q去钓鱼,拿鱼网捞一下,有没有鱼立即知道,不用等,直接就捞(同步非阻塞);
小Q去钓鱼,这个鱼缸比较牛皮,扔了后自己就打王者荣耀去了,因为鱼上钩了这个鱼缸带的报警器会通知我。这样实现异步(异步非阻塞)
进程间的通信方式有哪些?
1.管道:在学习Linux基本命令使用的时候,我们经常通过多个命令的组合来完成我们的需求。比如说我们想知道如何查看进程或者端口是否在使用,会使用下面的这条命令。
netstat -nlp | grep XXX 这里的"|“实际上就是管道的意思。”|“前面部分作为”|“后面的输入,很明显是单向的传输,这样的管道我们叫做"匿名管道”,自行创建和销毁。既然有匿名管道,应该就有带名字的管道"命名管道"。如果你想双向传输,可以考虑使用两个管道拼接即可。
管道通信方式的优点:简单,我们平时经常使用以致于都不知道这是管道。
管道通信方式的缺点:效率很低,因为假设现在有AB两个进程,A进程将数据写入管道,B进程需要等待A进程将信息写完以后才能读出来,所以这种方案不适合频繁的通信。
2.消息队列:消息队列在发送数据的时候,按照一个个独立单元(消息体)进行发送,其中每个消息体规定大小块,同时发送方和接收方约定好消息类型或者正文的格式。
消息队列的优点:在管道中,其大小受限且只能承载无格式字节流的方式,而消息队列允许不同进程以消息队列的形式发送给任意的进程。
消息队列的缺点:数据太大时,需要拷贝的时间也就越多。
3.共享内存:每个进程都有自己的虚拟内存空间,不同的进程映射到不同的物理内存空间。那么我们可以申请一块虚拟地址空间,不同进程通过这块虚拟地址空间映射到相同的物理地址空间。这样不同进程就可以及时感知进程做了什么,就不需要再拷贝来拷贝去。通过shmget创建一份共享内存,并可以通过ipcs命令查看我们创建的共享内存。此时如果一个进程需要访问这段内存,需要将这个内存加载到自己虚拟地址空间的一个位置,让内核给它一个合法地址。使用完毕接触板顶并删除内存对象。
共享内存的缺点:许多进程都共享这块内存,如果同时都往里面写内容,难免会出现冲突的现象,比如A进程写了数字5,B进程同样的地址写了6就直接给覆盖了,这样就不友好了?
4.信号量:为了防止冲突,我们得有个约束或者说一种保护机制。使得同一份共享的资源只能一个进程使用,这里就出现了信号量机制。信号量实际上是一个计数器,这里需要注意下,信号量主要实现进程之间的同步和互斥,而不是存储通信内容。信号量定义了两种操作,p操作和v操作,p操作为申请资源,会将数值减去M,表示这部分被他使用了,其他进程暂时不能用。v操作是归还资源操作,告知归还了资源可以用这部分。
5.信号:在操作系统中,不同信号用不同的值表示,每个信号设置相应的函数,一旦进程发送某一个信号给另一个进程,另一进程将执行相应的函数进行处理。也就是说把可能出现的异常等问题准备好,一旦信号产生就执行相应的逻辑即可。
6.套接字:上面的几种方式都是单机情况下多个进程的通信方式,如果我想和相隔几千里的计算机通信怎么办?这就需要套接字socket,生活中极常见,因为我们天天请求浏览器给予的响应。
进程的调度算法有哪些?
调度算法是指:调度程序是内核的重要组成部分,决定着下一个要运行的进程。那么根据系统的资源分配策略所规定的资源分配算法。
先来先服务调度算法:如同队列的先进先出特性,每一次的调度都从队列中选择最先进入队列的投入运行。
时间片轮转调度法:如果进程在当前的时间片运行结束,直接将进程从队列移除。如果进程在这个时间片跑完都没有结束,进程变为等待状态,放在进程尾部直到所有进程执行完毕。
短作业优先调度算法:「短作业」意味着执行时间比较短,「优先」代表执行顺序。
最短剩余时间优先:最短剩余时间是针对最短进程优先增加了抢占机制的版本。在这种情况下,进程调度总是选择预期剩余时间最短的进程。当一个进程加入到就绪队列时,他可能比当前运行的进程具有更短的剩余时间,只要新进程就绪,调度程序就能可能抢占当前正在运行的进程。像最短进程优先一样,调度程序正在执行选择函数是必须有关于处理时间的估计,并且存在长进程饥饿的危险。
高响应比优先调度算法:有响应之前应该会有请求,相当于是请求+响应+优先,算是一种综合的调度算法。也就是它结合了短作业优先,先来先服务以及长作业的一些特性。首先来说短作业优先。等待时间我们假设相等,服务时间很短,这样的话短作业就会有更高的优先权。再来看先来先服务。假设服务时间相同,先来的自然等待时间较长,优先级越高。上面说长作业很可能因为等待时间过长,容易饿死。
优先级调度算法:优先级调度算法每次从后备作业队列中选择优先级最髙的一个或几个作业,将它们调入内存,分配必要的资源,创建进程并放入就绪队列。在进程调度中,优先级调度算法每次从就绪队列中选择优先级最高的进程,将处理机分配给它,使之投入运行。
协程比线程的优势
1、减少了线程切换的成本。Java 中的线程,不管是创建还是切换,都需要较高的成本。
子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,
和多线程比,线程数量越多,协程的性能优势就越明显。这也就是说,协程的效率比较高。
2、协程的第二大优势就是,不需要多线程的锁机制,因为只有一个线程,
也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,
所以执行效率比多线程高很多。
3、协程更轻量级。创建一个线程栈大概需要 1M 左右,
而协程栈大概只需要几 K 或者几十 K。
有优势也有劣势,因为程语句执行时,顺序不定
所以,协程看起来也没那么好控制。
手写智能指针类
template <class T> class SmartPointer {
public:
//普通构造函数, 设定T * ptr的值,并将引用计数设为1
SmartPointer(T * ptr) {
ref = ptr;
ref_count = new unsigned;
*ref_count = 1;
}
//指针拷贝构造函数,新建一个指向已有对象的智能指针
//需要先设定ptr和ref_count
//设为指向sptr的ptr和ref_count
//并且,因为新建了一个ptr的引用,所以引用计数加一
SmartPointer(SmartPointer<T> &sptr) {
ref = sptr.ref;
ref_count = sptr.ref_count;
++(*ref_count);
}
//rewrite "="
SmartPointer<T> & operator = (SmartPointer<T> &sptr) {
//同一个指针,直接返回
if (this == &sptr)
return *this;
//如果计数值大于1,则旧指针计数值-1
if(*ref_count > 0)
remove();
//把旧指针值给新指针
ref = sptr.ref;
ref_count = sptr.ref_count;
//指针计数+1
++(*ref_count);
return *this;
}
~SmartPointer() {
remove();
}
T getValue() {
return *ref;
}
T getCount() {
return static_cast<T>(*ref_count);
}
protected:
//删除指针
void remove() {
--(*ref_count);
//如果计数值等于0,则销毁指针,并执行析构函数
if (*ref_count == 0) {
delete ref;
delete ref_count;
ref = NULL;
ref_count = NULL;
}
}
private:
unsigned * ref_count; //应用计数值
T * ref; //普通指针
};
sql 查找平均分大于90的学号和平均分
SELECT
sno,
AVG(grade)
FROM
sc
GROUP BY
sno
HAVING
AVG(grade) >= 90;