1、什么是进程?
- 进程是指在系统中正在运行的一个应用程序,程序一旦运行就是进程;
- 进程可以认为是程序执行的一个实例,进程是系统进行资源分配的最小单位,且每个进程拥有独立的地址空间;
- 一个进程无法直接访问另一个进程的变量和数据结构,如果希望一个进程去访问另一个进程的资源,需要使用进程间的通信,比如:管道、消息队列等;
- 线程是进程的一个实体,是进程的一条执行路径;比进程更小的独立运行的基本单位,线程也被称为轻量级进程,一个程序至少有一个进程,一个进程至少有一个线程;
- 进程状态: 有运行、阻塞、就绪三个基本状态;
- 进程调度算法: 先来先服务调度算法、短作业优先调度算法、非抢占式优先级调度算法、抢占式优先级调度算法、高响应比优先调度算法、时间片轮转法调度算法。
2、什么是线程?
- 线程是进程的一个实体,是CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
- 线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
3、进程和线程的区别?
- 同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间;
- 同一进程内的线程共享本进程的资源,但是进程之间的资源是独立的;
- 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃
整个进程崩溃,所以多进程比多线程健壮; - 进程切换,消耗的资源大。所以涉及到频繁的切换,使用线程要好于进程;
- 两者均可并发执行;
- 进程可以独立执行,每个独立的进程有一个程序的入口、程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
4、一个进程可以创建多少线程,和什么有关?
理论上,一个进程可用虚拟空间是2G,默认情况下,线程的栈的大小是1MB,所以理论上最多只能创建2048个线程。如果要创建多于2048的话,必须修改编译器的设置。
因此,一个进程可以创建的线程数由可用虚拟空间和线程的栈的大小共同决定,只要虚拟空间足够,那么新线程的建立就会成功。
如果需要创建超过2K以上的线程,减小你线程栈的大小就可以实现了,虽然在一般情况下,你不需要那么多的线程。
过多的线程将会导致大量的时间浪费在线程切换上,给程序运行效率带来负面影响。
5、外中断和异常有什么区别?
外中断是指由 CPU 执行指令以外的事件引起,如 I/O 完成中断,表示设备输入/输出处理已经完成,处理器能够发送下一个输入/输出请求。此外还有时钟中断、控制台中断等。
异常是由 CPU 执行指令的内部事件引起,如非法操作代码、地址越界、算术溢出等。
6、进程线程模型你知道多少?
对于进程和线程的理解和把握可以说基本奠定了对系统的认知和把控能力。其核心意义绝不仅仅是“线程是调度的基本单位,进程是资源分配的基本单位”这么简单。
多线程
这里讨论的是用户态的多线程模型,同一个进程内部有多个线程,所有的线程共享同一个进程的内存空间,进程中定义的全局变量会被所有的线程共享,比如有全局变量int i = 10
,这一进程中所有并发运行的线程都可以读取和修改这个i的值,而多个线程被CPU调度的顺序又是不可控的,所以对临界资源的访问尤其需要注意安全。
我们必须知道,做一次简单的i = i + 1在计算机中并不是原子操作,涉及内存取数,计算和写入内存几个环节, 而线程的切换有可能发生在上述任何一个环节中间,所以不同的操作顺序很有可能带来意想不到的结果。
但是,虽然线程在安全性方面会引入许多新挑战,但是线程带来的好处也是有目共睹的。首先,原先顺序执行的程序(暂时不考虑多进程)可以被拆分成几个独立的逻辑流,这些逻辑流可以独立完成一些任务(最好这些任务是不相关的)。
比如 QQ 可以一个线程处理聊天一个线程处理上传文件,两个线程互不干涉,在用户看来是同步在执行两个任务, 试想如果线性完成这个任务的话,在数据传输完成之前用户聊天被一直阻塞会是多么尴尬的情况。
对于线程,我认为弄清以下两点非常重要:
- 线程之间有无先后访问顺序?(线程依赖关系)
- 多个线程共享访问同一变量?(同步互斥问题)
同一进程的多个线程共享进程的资源,但是每个线程特有的部分却很少提及,除了标识线程的tid,每个线程还有自己独立的栈空间,线程彼此之间是无法访问其他线程栈上内容的。
而作为处理机调度的最小单位,线程调度只需要保存线程栈、寄存器数据和PC即可,相比进程切换开销要小很多。
线程相关接口不少,主要需要了解各个参数意义和返回值意义。
1、线程创建和结束
背景知识:
- 在一个文件内的多个函数通常都是按照main函数中出现的顺序来执行;
- 但是在分时系统下,我们可以让每个函数都作为一个逻辑流并发执行,最简单的方式就是采用多线程策略;
- 在main函数中调用多线程接口创建线程,每个线程对应特定的函数(操作),这样就可以不按照main函数中各个函数出现的顺序来执行,避免了忙等的情况。
线程基本操作的接口如下:
创建线程:
int pthread_create(pthread_t *tidp,const pthread_attr_t *attr, void *(start_rtn)(void),void *arg);
创建一个新线程,pthread和start_routine不可或缺,分别用于标识线程和执行体入口,其他可以填NULL。
tidp
:用来返回线程的tid,*tidp值即为tid,类型pthread_t == unsigned long int。attr
:指向线程属性结构体的指针,用于改变所创线程的属性,填NULL使用默认值。start_rtn
:线程执行函数的首地址,传入函数指针。arg
:通过地址传递来传递线程函数参数,这里是无符号类型指针,可以传任意类型变量的地址,在被传入函数中先强制类型转换成所需类型即可。
获得线程ID:
pthread_t pthread_self();
调用时,会打印线程ID。
等待线程结束:
int pthread_join(pthread_t tid, void** retval);
主线程调用,等待子线程退出并回收其资源, 类似于进程中wait/waitpid回收僵尸进程,调用pthread_join的线程会被阻塞。
tid
:创建线程时通过指针得到tid值。retval
:指向返回值的指针。
结束线程:
pthread_exit(void *retval);
子线程执行,用来结束当前线程并通过retval传递返回值,该返回值可通过pthread_join获得。
retval
:指向返回值的指针。
分离线程:
int pthread_detach(pthread_t tid);
主线程、子线程均可调用。 主线程中pthread_detach(tid),子线程中pthread_detach(pthread_self()),调用后和主线程分离,子线程结束时自己立即回收资源。
tid
:创建线程时通过指针得到tid值。
2、线程属性值修改
背景知识:
线程属性对象类型为pthread_attr_t,结构体定义如下:
typedef struct{
int etachstate; // 线程分离的状态
int schedpolicy; // 线程调度策略
struct sched_param schedparam; // 线程的调度参数
int inheritsched; // 线程的继承性
int scope; // 线程的作用域
// 以下为线程栈的设置
size_t guardsize; // 线程栈末尾警戒缓冲大小
int stackaddr_set; // 线程的栈设置
void * stackaddr; // 线程栈的位置
size_t stacksize; // 线程栈大小
}pthread_arrt_t;
相关接口:
- 对上述结构体中各参数大多有:pthread_attr_get()和pthread_attr_set()系统调用函数来设置和获取。这里不一一罗列
多进程
每一个进程是资源分配的基本单位。
进程结构由以下几个部分组成:代码段、堆栈段、数据段。代码段是静态的二进制代码,多个程序可以共享。
实际上在父进程创建子进程之后,父、子进程除了pid外,几乎所有的部分几乎一样。
父、子进程共享全部数据,但并不是说他们就是对同一块数据进行操作,子进程在读写数据时会通过写时复制机制将公共的数据重新拷贝一份,之后在拷贝出的数据上进行操作。(读时共享,写时复制)
如果子进程想要运行自己的代码段,还可以通过调用execv()函数重新加载新的代码段,之后就和父进程独立开了。
我们在shell中执行程序就是通过shell进程先fork()一个子进程再通过execv()重新加载新的代码段的过程。
1、进程创建与结束
背景知识:
进程有两种创建方式,一种是操作系统创建的,一种是父进程创建的。
相关接口:
创建进程:
pid_t fork(void);
返回值: 出错返回-1;父进程中返回pid > 0;子进程中pid == 0
结束进程:
void exit(int status);
status是退出状态,保存在全局变量中,通常0表示正常退出。
获得PID:
pid_t getpid(void);
返回调用者pid。
获得父进程PID:
pid_t getppid(void);
返回父进程pid。
其他补充:
正常退出方式: exit()、_exit()、return(在main中)。
- exit()和_exit()区别:exit()是对__exit()的封装,都会终止进程并做相关收尾工作,最主要的区别是
_exit()
函数关闭全部描述符和清理函数后不会刷新流,但是exit()会在调用_exit()
函数前刷新数据流。 - return和exit()区别:exit()是函数,但有参数,执行完之后控制权交给系统。return若是在调用函数中,执行完之后控制权交给调用进程,若是在main函数中,控制权交给系统。
异常退出方式: abort()、终止信号。