进程线程高频题

1.进程与线程的区别?

进程就是运行的程序
(1)进程是资源分配的最小单位,线程是cpu调度的最小单元
(2)不同进程的有自己独立的地址空间。(地址空间大小为4G,分为内核空间和用户空间,内核空间为所有进程共享,用户空间是独立的)。同一进程的所有线程共享该进程的资源(代码段,进程打开的文件描述符等),仅有一些寄存器栈资源是独立的(线程id、寄存器的值、栈、线程的优先级和调度策略、线程的私有数据、信号屏蔽字、errno变量)。所以进程切换开销大,线程切换开销小。
(3)进程间有专门的通信方式:管道、消息队列、共享内存、信号量等。但是同一进程下的线程共享全局变量、静态变量等数据,通信更方便。
无名管道(pipe):半双工,只能用于亲缘关系的进程间通信。
有名管道(fifo):半双工,允许无亲缘关系的进程间通信。
信号量:计数器,用来控制多个进程对共享资源的访问。常作为锁机制。
消息队列:
信号:用于通知接收进程某个事件已经发生。
共享内存:最快的IPC方式。
套接字:
文件

线程间的通信机制:主要用于线程同步
1.线程锁机制:
(1)互斥锁:提供了以排他方式防止数据结构被并发修改的方法
(2)条件变量:与互斥锁一起使用,可以以原子的方式进行阻塞进程,直到某个特定条件为真为止。
(3)读写锁:允许多个线程同时读共享数据,对写操作是互斥的
2.信号量机制
3.信号机制

多线程锁的种类有哪些?
a.互斥锁(mutex)b.递归锁 c.自旋锁 d.读写锁
自旋锁和互斥锁的区别?
当锁被其他线程占用时,其他线程并不是睡眠状态,而是不停的消耗CPU,获取锁;互斥锁则不然,保持睡眠,直到互斥锁被释放激活。

自旋锁,递归调用容易造成死锁,对长时间才能获得到锁的情况,使用自旋锁容易造成CPU效率低,只有内核可抢占式或SMP情况下才真正需要自旋锁。

(4)多进程更健壮。多线程程序只要有一个线程挂了,整个进程也就挂了。但是进程挂掉不会对另外的进程造成影响。

2.进程的状态:

创建,就绪,运行,阻塞,终止
创建态:申请一个空的进程控制块(PCB),填入用于控制和管理进程的信息,然后为该进程分配运行时所必须的资源,最后把该进程转入就绪态插入到就绪队列中。
就绪态:获取了除CPU之后的所有资源,只要处理器分配资源马上可以执行。
运行态:进程获取CPU。
阻塞态:正在执行的进程由于某些事件的发生(I/O请求等)暂时无法继续执行,此时引起进程调度,操作系统把处理机分配给另外一个就绪的进程。
终止态:

3.死锁?产生条件?如何避免死锁?

在两个及以上的并发进程中,每个进程都拥有某些资源而又等待别的进程释放他们持有的资源。
原因:系统资源不足,进程推进顺序非法
产生死锁的必要条件:
(1)互斥条件:一个资源每次只能被一个进程使用
(2)不可剥夺条件:进程已获得资源,在未使用之前,不能强行剥夺
(3)请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有。
(4)循环等待条件:资源循环等待。

死锁的解除和预防:不让这四个必要条件成立。对资源的分配要给予合理规划。

4.进程间同步与互斥的区别?线程同步的方式?

互斥:指某一个资源同时只允许一个访问者对其进行访问。排他性
同步:在互斥的基础上,通过其他机制实现访问者对资源的有序访问。协作性

同步机制遵循的原则:
空闲让进,忙则等待,有限等待,让权等待。

线程同步是指多个线程同时访问某资源时,采用一系列的机制以保证最多只能一个线程访问该资源。
线程同步的方式:
临界区:通过对多线程的串行化来访问公共资源,速度快,适合控制数据访问。(非内核对象,工作在用户方式下,不能跨进程)
后面三个为内核对象,多个进程间的各个线程间实现同步。
互斥量:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。
信号量:允许多个线程同一时刻访问统一资源,但是限制数目。
事件(信号):通过通知操作的方式来保持多线程的同步,还可以方便实现多线程的优先级比较操作。

5.进程调度算法:

(1)先来先服务:按照进程进入就绪队列的先后次序选择
(2)短作业优先:
(3)时间片轮转:
(4)高响应比优先:
(5)优先权调度:
(6)多级队列调度:

6.什么时候用多线程?什么时候用多进程?

多线程:
(1)需要频繁创建销毁
(2)需要进行大量计算
(3)对资源的管理和保护要求不高时

多线程程序架构,线程数量应该如何设置?

应尽量和CPU核数相等或者为CPU核数+1的个数

6.什么是原子操作,gcc提供的原子操作原语,使用这些原语如何实现读写锁?

原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch。

7.网络编程设计模式,reactor/proactor/半同步半异步模式?

reactor模式:同步阻塞I/O模式,注册对应读写事件处理器,等待事件发生进而调用事件处理器处理事件。 proactor模式:异步I/O模式。Reactor和Proactor模式的主要区别就是真正的读取和写入操作是有谁来完成的,Reactor中需要应用程序自己读取或者写入数据,Proactor模式中,应用程序不需要进行实际读写过程。

Reactor是:

主线程往epoll内核上注册socket读事件,主线程调用epoll_wait等待socket上有数据可读,当socket上有数据可读的时候,主线程把socket可读事件放入请求队列。睡眠在请求队列上的某个工作线程被唤醒,处理客户请求,然后往epoll内核上注册socket写请求事件。主线程调用epoll_wait等待写请求事件,当有事件可写的时候,主线程把socket可写事件放入请求队列。睡眠在请求队列上的工作线程被唤醒,处理客户请求。

Proactor:

主线程调用aio_read函数向内核注册socket上的读完成事件,并告诉内核用户读缓冲区的位置,以及读完成后如何通知应用程序,主线程继续处理其他逻辑,当socket上的数据被读入用户缓冲区后,通过信号告知应用程序数据已经可以使用。应用程序预先定义好的信号处理函数选择一个工作线程来处理客户请求。工作线程处理完客户请求之后调用aio_write函数向内核注册socket写完成事件,并告诉内核写缓冲区的位置,以及写完成时如何通知应用程序。主线程处理其他逻辑。当用户缓存区的数据被写入socket之后内核向应用程序发送一个信号,以通知应用程序数据已经发送完毕。应用程序预先定义的数据处理函数就会完成工作。

半同步半异步模式:

上层的任务(如:数据库查询,文件传输)使用同步I/O模型,简化了编写并行程序的难度。
而底层的任务(如网络控制器的中断处理)使用异步I/O模型,提供了执行效率。

8.有一个计数器,多个线程都需要更新,会遇到什么问题,原因是什么,应该如何做?如何优化?

有可能一个线程更新的数据已经被另外一个线程更新了,更新的数据就会出现异常,可以加锁,保证数据更新只会被一个线程完成。

9.如果select返回可读,结果只读到0字节,什么情况?

某个套接字集合中没有准备好,可能会select内存用FD_CLR清为0.

10. connect可能会长时间阻塞,怎么解决?

1.使用定时器;(最常用也最有效的一种方法)
2.采用非阻塞模式:设置非阻塞,返回之后用select检测状态。

11.keepalive 是什么东西?如何使用?

keepalive,是在TCP中一个可以检测死连接的机制。
1).如果主机可达,对方就会响应ACK应答,就认为是存活的。
2).如果可达,但应用程序退出,对方就发RST应答,发送TCP撤消连接。
3).如果可达,但应用程序崩溃,对方就发FIN消息。
4).如果对方主机不响应ack, rst,继续发送直到超时,就撤消连接。默认二个小时。

12.socket什么情况下可读?

1.socket接收缓冲区中已经接收的数据的字节数大于等于socket接收缓冲区低潮限度的当前值;对这样的socket的读操作不会阻塞,并返回一个大于0的值(准备好读入的数据的字节数).

2.连接的读一半关闭(即:接收到对方发过来的FIN的TCP连接),并且返回0;
3.socket收到了对方的connect请求已经完成的连接数为非0.这样的soocket处于可读状态;
4.异常的情况下socket的读操作将不会阻塞,并且返回一个错误(-1)。

13.udp调用connect有什么作用?

1).因为UDP可以是一对一,多对一,一对多,或者多对多的通信,所以每次调用sendto()/recvfrom()时都必须指定目标IP和端口号。通过调用connect()建立一个端到端的连接,就可以和TCP一样使用send()/recv()传递数据,而不需要每次都指定目标IP和端口号。但是它和TCP不同的是它没有三次握手的过程。

2).可以通过在已建立连接的UDP套接字上,调用connect()实现指定新的IP地址和端口号以及断开连接。

14. socket编程,如果client断电了,服务器如何快速知道?

使用定时器(适合有数据流动的情况);

使用socket选项SO_KEEPALIVE(适合没有数据流动的情况);

1)、自己编写心跳包程序,简单的说就是自己的程序加入一条线程,定时向对端发送数据包,查看是否有ACK,根据ACK的返回情况来管理连接。此方法比较通用,一般使用业务层心跳处理,灵活可控,但改变了现有的协议;
2)、使用TCP的keepalive机制,UNIX网络编程不推荐使用SO_KEEPALIVE来做心)跳检测。
keepalive原理:TCP内嵌有心跳包,以服务端为例,当server检测到超过一定时间(/proc/sys/net/ipv4/tcp_keepalive_time 7200 即2小时)没有数据传输,那么会向client端发送一个keepalive packet。

如何实现守护进程?

1)创建子进程,父进程退出
2)在子进程中创建新会话
3)改变当前目录为根目
4)重设文件权限掩码
5) 关闭文件描述符
6) 守护进程退出处理
当用户需要外部停止守护进程运行时,往往会使用 kill命令停止该守护进程。所以,守护进程中需要编码来实现kill发出的signal信号处理,达到进程的正常退出。

exit() _exit()的区别?

1._exit()执行后会立即返回给内核,而exit()要先执行一些清除操作,然后将控制权交给内核。
2.调用_exit()函数时,其会关闭进程所有的文件描述符,清理内存,以及其他一些内核清理函数,但不会刷新流(stdin 、stdout、stderr……)。exit()函数是在_exit()函数上的一个封装,它会调用_exit,并在调用之前先刷新流。
3.exit()函数与_exit()函数最大的区别就在于,exit()函数在调用exit系统之前要检查文件的打开情况,把文件缓冲区的内容写回文件。

标准库函数和系统调用的区别?

系统调用:是操作系统为用户态运行的进程和硬件设备(如CPU、磁盘、打印机等)进行交互提供的一组接口,即就是设置在应用程序和硬件设备之间的一个接口层。
库函数:把函数放到库里。是把一些常用到的函数编完放到一个lib文件里,供别人用。别人用的时候把它所在的文件名用#include<>加到里面就可以了。一类是c语言标准规定的库函数,一类是编译器特定的库函数。
系统调用是为了方便使用操作系统的接口,而库函数则是为了人们编程的方便。

系统如何将一个信号通知到进程?

内核给进程发送信号,是在进程所在的进程表项的信号域设置对应的信号的位。进程处理信号的时机就是从内核态即将返回用户态的时候。执行用户自定义的信号处理函数的方法很巧妙。把该函数的地址放在用户栈栈顶,进程从内核返回到用户态的时候,先弹出信号处理函数地址,于是就去执行信号处理函数了,然后再弹出,才是返回进入内核时的状态。

fork()一子进程后父进程的全局变量能不能使用?

fork后子进程将会拥有父进程的几乎一切资源,父子进程的都各自有自己的全局变量。不能通用,不同于线程。对于线程,各个线程共享全局变量。

linux的五种IO模式/异步模式.

1)同步阻塞I/O

2)同步非阻塞I/O

3)同步I/O复用模型

4) 同步信号驱动I/O

5) 异步I/O模型

IO多路复用之select、poll、epoll

https://www.cnblogs.com/haimishasha/p/10606714.html
IO多路复用:通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

select,poll,epoll,本质上都是同步I/O。因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

  • select:

select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。

int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)

 - maxfdp1指定待测试的描述字个数,它的值是待测试的最大描述字加1
 - readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述字。实际是bitmap,把对应的fd置位代表有读//异常事件发生。

	void FD_ZERO(fd_set *fdset);           //清空集合
	void FD_SET(int fd, fd_set *fdset);   //将一个给定的文件描述符加入集合之中
	void FD_CLR(int fd, fd_set *fdset);   //将一个给定的文件描述符从集合中删除
	int FD_ISSET(int fd, fd_set *fdset);   // 检查集合中指定的文件描述符是否可以读写 

 - timeout指定等待的时间,告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和**微秒数**。

     这个参数有三种可能:
    (1)永远等待下去(NULL):仅在有一个描述字准备好I/O时才返回。
    (2)等待一段固定时间:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。
    (3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0
sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(2000);
addr.sin_addr.s_addr = INADDR_ANY;
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
listen(sockfd, 5);

for (i = 0; i < 5; i++) {
    memset(&client, 0, sizeof(client));
    addrlen = sizeof(clinet);
    fds[i] = accept(sockfd, (struct sockaddr*)&client, &addrlen);
    if (fds[i] > max) {
        max = fds[i];//记录最大的fd
    }
}

//开始select调用
while(1) {
    //每次都需要重新置位,因为select返回的rset已经改变
    FD_ZERO(&rset);
    for (int i = 0; i < 5; i++) {
        FD_SET(fds[i], &rset);
    }    
}
select(max+1, &rset, NULL, NULL, NULL);//若没有读事件,将会阻塞在这

//有数据的话,内核会把rset置位,select返回
for (int i = 0; i < 5; i++) {
    //轮询rset判断哪个fd有读事件
    if(FD_ISSET(fds[i], &rset)) {
        memset(buffer, 0 ,MAXBUF);
        read(fds[i], buffer, MAXBUF);//读
        puts(buffer);//处理
    }
}

优点:
1.跨平台;
2.时间精度高。
缺点:
1.最大限制:单个进程能够监视的文件描述符的数量存在最大限制。(基于数组存储)FD_SETSIZE设置,32位机默认是1024,63位默认是2048
2.时间复杂度:对socket采用轮询的方法进行线性扫描,效率低,时间复杂度是O(n).
3.内存拷贝:内核需要将消息传递到用户空间(rset),复制开销大。

  • poll:

本质上和select没有区别,基于链表来存储,没有最大连接数的限制。其他缺点和select相同。
优化针对pollfd这个数据结构展开:

	int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
	struct pollfd {
      int fd;            /* 文件描述符 */
      short events;        /* 等待的事件,用户设置 */
      short revents;       /* 实际发生了的事件,内核在调用返回时设置这个域 */
    } ; 
   nfds---数组fds元素的个数
   timeout指定等待的毫秒数。
struct pollfd {
    int fd;
    short events;
    short revents;
}
for (i = 0; i < 5; i++) {
    memset(&client, 0, sizeof(client));
    addrlen = sizeof(clinet);
    pollfds[i].fd = accept(sockfd, (struct sockaddr*)&client, &addrlen);
    pollfds[i].events = POLLIN;    
}

//开始poll调用
poll(pollfds, 5, 50000);//若没有读事件,将会阻塞在这

//有数据的话,内核会把对应的revents置位,poll返回
for (int i = 0; i < 5; i++) {
    //轮询pollfds中的fd判断哪个revents有读事件
    if(pollfds[i].revents & POLLIN){
        pollfds[i].revents = 0;//清除
        memset(buffer, 0 ,MAXBUF);
        read(pollfds[i].fd, buffer, MAXBUF);//读
        puts(buffer);//处理
    }
}
  • epoll:

通过epoll_create创建一个epoll句柄,size来告诉内核这个监听数目一共有多大,返回epfd。然后将要监听的fd和事件类型通过epoll_ctl注册到epfd。最后通过epoll_wait等待事件的发生,若注册的文件描述符对应的事件触发了,会调用回调函数通知。

struct epoll_event events[5];
struct epoll_event { 
   __uint32_t events; /* Epoll events */ 
   epoll_data_t data; /* User data variable */ 
};
/*创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。*/
int epfd = epoll_create(10);

for (int i = 0; i < 5; i++) {
    static struct epoo_event ev;
    memset(&client, 0, sizeof(client));
    addrlen = sizeof(clinet);
    ev.data.fd = accept(sockfd, (struct sockaddr*)&client, &addrlen);
    ev.events = POLLIN;  
    //epoll的事件注册函数. 
    epoll_ctrl(epfd, EPOLL_CTL_ADD, ev.data.fd, &ev);
}

while(1) {
    //等待事件的产生
    nfds = epoll_wait(epfd, events, 5, 10000);
    //没有数据的时候阻塞,有数据的时候重排(将有数据的fd放到最前面)所以只遍历返回的前nfds个fd即可
    for (i = 0; i < nfds; i++) {
        memset(buffer, 0 ,MAXBUF);
        read(events[i].data.fd, buffer, MAXBUF);//读
        puts(buffer);//处理
    }
}

优点:
1.没有最大连接数的限制。基于红黑树和双向链表存储,1G内存上能监听10万个端口
2.时间复杂度低:边缘触发和事件驱动,监听回调,时间复杂度为O(1)
3.利用mmap()把对应设备文件映射到用户空间上,消息传递不通过内核,减少拷贝开销。
缺点:依赖于操作系统linux

适合epoll的应用场景:连接特别多,活跃连接特别少。

epoll两种模式:LT(水平触发,默认);ET(边缘触发)
LT:只要有数据就触发,缓冲区剩余未读尽的数据会导致epoll_wait都会返回它的事件
ET:只有新数据到来才触发,不管缓冲区是否还有数据,缓冲区剩余未读尽的数据不会导致epoll_wait返回。
ET模式在很大程度上减少了epoll事件被重复触发的次数,效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞接口,避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

  1. 在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。此处去掉了遍历文件描述符,而是通过监听回调的的机制。这正是epoll的魅力所在。
  2. 如果没有大量的idle-connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当遇到大量的idle-connection,就会发现epoll的效率大大高于select/poll。
    epoll应用场景
    适合用epoll的应用场景:  对于连接特别多,活跃的连接特别少  典型的应用场景为一个需要处理上万的连接服务器,例如各种app的入口服务器,例如qq不适合epoll的场景:  连接比较少,数据量比较大,例如sshepoll 的惊群问题:  因为epoll 多用于多个连接,只有少数活跃的场景,但是万一某一时刻,epoll 等的上千个文件描述符都就绪了,这时候epoll 要进行大量的I/O,此时压力太大。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值