1. 进程和线程的区别是什么?进程间通信如何实现,线程间通信如何实现?
进程通信的方式:1管道、有名管道2信号3信号量4共享内存5消息队列6套接字;
# 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
# 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
# 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
# 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
# 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
# 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
# 套接字( socket ) : 套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机间的进程通信。
进程间通信的缺点:进程间通信较为复杂,需要通过操作系统(也就是内核)调用;多线程共享地址空间和数据空间,所以多个线程间的通信是一个线程的数据可以直接提供给其他线程使用,而不必通过操作系统(也就是内核的调度)。
线程通信方式:1锁机制 2信号量 3信号;
由于同一个进程之间的线程共享进程的全局变量和内存,因此线程间通信简单,可以直接通信。但正是因为线程之间共享数据,所以会造成某些共享数据的互斥问题。
# 锁机制:包括互斥锁、条件变量、读写锁
*互斥锁提供了以排他方式防止数据结构被并发修改的方法。
*读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
*条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
# 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
# 信号机制(Signal):类似进程间的信号处理
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
2.死锁
进程之间由于竞争资源而造成的一种互相等待的状态。
4个必要条件
独占、不可剥夺、请求和保持、循环等待;
思死锁的处理策略:
1.预防死锁 :破坏四个必要条件之一即可 预先静态分配资源(破坏请求和保持);顺序资源分配法(破坏循环等待)
2.死锁避免: 银行家算法
3.死锁检测和解除: 资源剥夺法(挂起某些死锁进程,将资源分配给其他死锁进程);撤销进程法(撤销一个或一部分死锁进程并剥夺资源);进程回退法(一个或多个进程回退到足以避免死锁的地步,回退时资源放弃资源)
3.socket通信基本原理
1. 本机上,区分两个进程是用pid; 但在网络上,是用IP地址+协议+端口号来区分两个不同进程。
2.利用socket建立连接的过程:
(1)服务器监听(服务器端处于等待链接);
(2)客户端请求(客户端发出连接请求);
(3)连接确认 (服务器端收到请求后响应此请求,建立一个线程,把套接字描述发送给客户端,客户端确认此描述,连接建立)
3. 服务器端: 创建socket;为socket绑定IP地址和端口号(bind);服务器端监听端口(listen);当服务器收到客户端连接请求后,被动打开,开始 接受客户端请求,并进入阻塞状态(accept方法会阻塞服务器端直到 收到客户端发送过来的连接状态信息,才能开始连接下一个客户端)
服务器端读取信息(recv) ; 服务器端关闭(close);
客户端: 客户端创建socket;根据服务器端的ip地址和端口连接服务器端socket(connect); 连接成功,向服务器发送连接状态信息;客户端向socket写入信息(send);客户端关闭(close)
4。 accept函数详解:
accept函数主要用于服务器端,一般位于listen函数之后,默认会阻塞进程,直到有一个客户请求连接,建立好连接后,它返回的一个新的套接字 socketfd_new ,此后,服务器端即可使用这个新的套接字socketfd_new与该客户端进行通信,而sockfd 则继续用于监听其他客户端的连接请求。
服务器端的端口在bind的时候已经绑定到了监听套接字socetfd所描述的对象上,accept函数新创建的socket对象其实并没有进行端口的占有,而是复制了socetfd的本地IP和端口号,并且记录了连接过来的客户端的IP和端口号。
由于TCP/IP协议栈是维护着一个接收和发送缓冲区的。在接收到来自客户端的数据包后,服务器端的TCP/IP协议栈应该会做如下处理:如果收到的是请求连接的数据包,则传给监听着连接请求端口的socetfd套接字,进行accept处理;如果是已经建立过连接后的客户端数据包,则将数据放入接收缓冲区。这样,当服务器端需要读取指定客户端的数据时,则可以利用socketfd_new 套接字通过recv或者read函数到缓冲区里面去取指定的数据(因为socketfd_new代表的socket对象记录了客户端IP和端口,因此可以鉴别)。
5. listen()维护两个队列,一个是未完成三次握手的,一个是已完成三次握手的,accept()是从已完成三次握手的队列中取出一个而已!
backlog参数的意义:默认最大的等待队列长度
listen只是把套接字从主动变为被动,并限制链接数;剩下的问题就是accept的,它会检测的;listen意思是监听:但是它不是一直在监听,accept才是;
理解:listen函数不会阻塞,它只是相当于把socket的属性更改为被动连接,可以接收其他进程的连接。listen侦听的过程并不是一直阻塞,直到有客户端请求连接才会返回,它只是设置好socket的属性之后就会返回。监听的过程实质由操作系统完成。但是accept会阻塞(也可以设置为非阻),如果listen的套接字对应的连接请求队列为空(没有客户端连接请求),它会一直阻塞等待。
6.多路复用:
IO多路复用,用于解决并发情况下socket通信问题。也即解决对多个I/O监听时,一个I/O阻塞影响其他I/O的问题。典型应用Nginx
7.处理客户端的并发请求
(1)使用多线程/多进程模型
(2)使用IO多路复用模型
(3)使用多线程 + IO多路复用模型
8.
select需要驱动程序的支持,驱动程序实现fops内的poll函数。select通过每个设备文件对应的poll函数提供的信息判断当前是否有资源可用(如可读或写),如果有的话则返回可用资源的文件描述符个数,没有的话则睡眠,等待有资源变为可用时再被唤醒继续执行。