点击蓝字关注我哦
以下是本期干货视频 视频后还附有文字版本哦▼《腾讯经典考点-讲一讲多线程多进程的优势和劣势》▼
ps:请在WiFi环境下打开,如果有钱任性请随意
1.题目的常见问法
我们在面试过程当中,面试官的问题都是具有引导性的,即由浅入深,由易到难。所以,一开始并不会直接问大家对比多线程和多进程的优劣势,以下是我们从各大公司面经当中总结出来面试官常考的点。题目的难度也是由浅入深,由易到难,供大家参考: 1.1 谈谈你所理解的进程; 1.2 谈谈你所理解的线程; 1.3 谈谈代码当中如何创建一个子进程; 1.4 谈谈代码当中如何创建一个线程; 1.5 谈谈多线程程序涉及到的线程安全问题; 1.6 谈谈多线程和多进程的区别以及各自的优劣点。2.考察的知识点
我们不难发现这类题目有两个基础的点, 进程和线程。只有搞清楚这两个基础知识点,那么回答这类问题就如鱼得水了。接下来,我们给大家介绍这两个基础的知识点。进程:
在大学中接触到课本里面,我们会得到一个结论:进程是程序的一个执行实例或者一个正在运行的程序。当我们将这样的答案告诉面试官的时候,并不能打动面试官。我们需要从更加深入的内核角度解释什么是进程。linux内核角度:
进程是担当分配系统资源(CPU时间,内存)的实体,在内核当中使用struct task_struct结构体进行描述符,也就是课本当中的进程控制块(PCB)。 既然提到了PCB,那我们就不得不阐述下PCB当中重要的东西: struct task_ struct内容分类当我们能够提及PCB当中的这些内容,基本上在回答进程概念的时候就能够打动面试官了。
- 标示符: 描述本进程的唯一标示符,用来区别其他进程
- 状态: 任务状态,退出代码,退出信号等
- 优先级: 相对于其他进程的优先级
- 程序计数器: 程序中即将被执行的下一条指令的地址
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
- 其他信息
多进程:
如何在linux环境下创建一个子进程?
我们在linux环境下使用操作系统提供的系统调用函数fork函数,来创建一个子进程:#include pid_t fork(void);
原理:
子进程会拷贝父进程的PCB,以及页表等结构, 创建子进程完成后,子进程具有自己的进程虚拟地址空间,具有自己的页表结构。
参数:该函数没有参数
返回值:
- 小于0:表示创建失败
- 等于0:返回给子进程
- 大于0:返回给父进程
参考代码:
#include #include int main(){ //创建子进程了 //系统调用函数 fork printf("linux66\n"); pid_t pid = fork(); if(pid < 0) { //没有内存的时候,可能会导致此结果;一定要理解:创建PCB是需要耗费内存的 perror("fork"); return 0; } else if(pid == 0) { //child printf("i am child:[%d]-[%d]\n", getpid(), getppid()); } else { //father printf("i am father:[%d]-[%d]\n", getpid(), getppid()); } return 0;}
线程:
有了上面回答进程的思路,我们在回答线程的时候,可以选择一下三个角度来阐述线程。从进程角度:
a.线程是“一个进程内部的控制序列“; b.一切进程至少都有一个执行线程; c.线程在进程内部运行,本质是在进程地址空间内运行。从资源分配角度:
a.在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化 b.透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。从内核角度:
a.在Linux系统中,线程被称之为轻量级进程,在内核中也是创建了struct task_struct结构体来描述线程,线程task_struct结构以当中的内存指针指向进程的虚拟地址空间; b.线程之间独有:linux下每一个线程都有自己的独有的线程id,栈空间,一组寄存器,errno,信号屏蔽字; c.线程之间共享:文件描述符表,信号的处理方式,当前进程工作目录,用户id和组id。多线程:
如何在linux环境下创建一个线程? 我们在linux环境下使用库函数pthread_create函数,来创建一个线程:#include int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
原理:
在linux操作系统下,创建一个线程,其实在操作系统内核创建了一个轻量级进程,同样线程的PCB也是拷贝与进程(或者理解为主线程)的PCB,但是线程PCB当中的内核指针指向进程的虚拟地址空间。
参数:
- pthread_t* thread:该参数是一个出参,返回线程的标识符,注意标识符和前面提到的线程ID是两码事
- const pthread_attr_t* attr : 表示创建线程的属性,传入NULL,则表示使用默认属性
- void* (*start_routine) (void *) :线程的入口函数,线程创建完毕之后,就是从该函数指针当中保存的函数地址开始运行,线程入口函数按照返回值为void *,参数为void *进行传递
- void *arg:为线程入口函数传递参数,可以传递C/C++内置类型,也可以传递结构体,类的实例化指针,this指针等
返回值:
- 小于0:创建失败
- 等于0:创建成功
注意事项:
- 编译多线程程序,需要在编译命令当中链接线程库,增加-lpthread;
- 如果要给线程入口函数传递参数,则最好在堆上开辟内存,防止临时变量出了作用域之后就被释放掉了;
- 由多线程程序引起的线程安全问题,也是需要我们童鞋掌握的知识。例如:如何保证线程之间的互斥与同步。
参考代码:
#include #include #include typedef struct thread_info{ int thread_num_; thread_info() { thread_num_ = -1; }}THREADINFO;void* thread_start(void* arg){ THREADINFO* ti = (THREADINFO*)arg; while(1) { printf("i am new thread~~~~: %d\n", ti->thread_num_); sleep(1); } //对于传递进来的对上开辟的内存,可以在线程入口函数结束的时候,释放掉,不会导致程序有内存泄漏的风险 delete ti; return NULL;}int main(){ pthread_t tid; int ret; int i = 0; for(; i < 4; i++) { THREADINFO* threadinfo = new thread_info(); //堆上开辟的 threadinfo->thread_num_ = i; ret = pthread_create(&tid, NULL, thread_start, (void*)threadinfo); if(ret < 0) { perror("pthread_create"); return 0; } } while(1) { printf("i am main thread~~~\n"); sleep(1); } return 0;}
总结:
进程是分配资源的基本单位, 线程是操作系统调度的基本单位。3.回答问题的思路
当我们明确了进程和线程的概念后,回归到我们的题目“多线程多进程的优势和劣势”, 总结一下几点回答问题的思路:- 先回答什么是进程以及多进程
- 再回答什么是线程以及多线程
- 最后在进行对比各自的优缺点
4.参考答案
- 进程是程序运行起来的一个实体,在内核当中创建了一个task_struct结构体来描述,该结构体当中比较重要的有,进程PID,进程状态,内存指针,上下文信息,程序计数器等。所以称进程是操作系统分配资源的基本单位;
- 在程序当中创建一个子进程可以使用到系统调用fork函数,该函数调用成功,返回两次,等于0返回给子进程,大于0返回给父进程,子进程的也是创建了一个task_struct结构体,该结构体是拷贝父进程的PCB的,但是子进程在创建完成之后具有自己的进程虚拟地址空间,所以进程之间是独立的;
- 线程是进程中的一个执行流,创建一个线程,也是在内核创建一个task_struct结构体,与创建子进程区别的是,线程task_struct结构体当中的内存指针是指向进程的虚拟地址空间。线程有自己独有的栈空间,所以避免了调用栈混乱的问题。所以称线程是操作系统调度的基本单位;
- 多线程程序可以并行执行,所以牵扯到线程安全的问题,我们可以使用互斥与同步计数来避免由于线程不安全导致的程序结果二义性的问题;
多进程程序的优点:
- 由于各自进程都有自己的进程虚拟地址空间,所以各个程序之间的运行不会互相干扰,运行稳定;
- 子进程崩溃,不会影响父进程,反之,同理;
- 多个进程可以利用充分利用多核cpu,并行的运行,不用担心对程序结果造成二义性。
多进程程序的缺点:
- 由于进程的独立性,所以不同进程之间的数据交换需要用到进程间通信;
- 创建一个子进程的开销要比创建一个线程的开销大,并且子进程占用的资源要比线程多;
- 子进程退出,需要父进程进行等待,否则会造成资源泄漏;
- 多进程程序切换比多线程程序切换,操作系统耗费的代价要大。
多线程程序的优点:
- 创建一个线程的开销小,线程占用的资源小;
- 多线程程序切换比多进程程序切换,操作系统耗费的代价小;
- 可以充分利用多核CPU,多线程程序中的线程可以并行运行,提高程序效率。
多线程程序的缺点:
- 代码编写难度增加;
- 程序的健壮性降低,一个线程的崩溃会导致整个程序的崩溃;
- 有可能的性能损失,多线程程序不是线程越多越好,由于多个线程之间的频繁切换的开销,如果大过业务执行的开销,则会对业务的性能有损失;
- 缺乏访问控制(线程不安全),多个线程并行执行,在操作临界资源的时候,可能会导致程序执行结果的二义性。
5.考点延伸
- 了解过守护进程吗?谈谈你的理解
- 谈谈多线程程序如何保证线程安全
- 谈谈互斥锁的加锁原理
- 谈谈线程同步使用到什么技术
- 谈谈你所理解的死锁的必要条件和预防死锁
- 聊聊生产者与消费者模型
- 谈谈你所理解的线程池
点亮"在看",点亮"offer"
原创不易,点个赞吧~