一、进程拥有哪些资源?
PID | 消耗内核资源 |
优先级 | 通过动态的优先级和静态的优先级来决定进程被CPU处理的顺序,静态优先级只能调用nice去修改,动态优先级[0,139]是调度程序通过或减少进程静态优先级来奖励IO消耗型进程或惩罚CPU消耗进程,调整后的优先级为动态优先级。 |
状态 | 七状态图 |
程序计数器 | 为了保证程序(在操作系统中理解为进程)能够连续地执行下去,CPU必须具有某些手段来确定下一条指令的地址。而程序计数器正是起到这种作用,所以通常又称为指令计数器。在程序开始执行前,必须将它的起始地址,即程序的一条指令所在的内存单元地址送入PC,因此程序计数器(PC)的内容即是从内存提取的第一条指令的地址。当执行指令时,CPU将自动修改PC的内容,即每执行一条指令PC增加一个量,这个量等于指令所含的字节数,以便使其保持的总是将要执行的下一条指令的地址。由于大多数指令都是按顺序来执行的,所以修改的过程通常只是简单的对PC加1。 当程序转移时,转移指令执行的最终结果就是要改变PC的值,此PC值就是转去的地址,以此实现转移。有些机器中也称PC为指令指针IP(Instruction Pointer) |
进程类型 | 实时进程和普通进程 |
文件描述符** | 分为普通文件和socket文件 |
内存 | 栈区:可伸缩;堆:自行分配 |
寄存器 | |
CPU | 1、CPU的三级缓存:CPU缓存(Cache Memory)是位于CPU与内存之间的临时存储器,它的容量比内存小的多但是交换速度却比内存要快得多。缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为CPU运算速度要比内存读写速度快很多,这样会使CPU花费很长时间等待数据到来或把数据写入内存。在缓存中的数据是内存中的一小部分,但这一小部分是短时间内CPU即将访问的,当CPU调用大量数据时,就可避开内存直接从缓存中调用,从而加快读取速度。CPU缓存可以分为一级缓存,二级缓存,部分高端CPU还具有三级缓存,每一级缓存中所储存的全部数据都是下一级缓存的一部分,这三种缓存的技术难度和制造成本是相对递减的,所以其容量也是相对递增的。当CPU要读取一个数据时,首先从一级缓存中查找,如果没有找到再从二级缓存中查找,如果还是没有就从三级缓存或内存中查找。一般来说,每级缓存的命中率大概都在80%左右,也就是说全部数据量的80%都可以在一级缓存中找到,只剩下20%的总数据量才需要从二级缓存、三级缓存或内存中读取,由此可见一级缓存是整个CPU缓存架构中最为重要的部分。 2**、ulimit -a 可设置进程资源(栈的大小,fd上限,创建进程个数) |
二、线程共享哪些资源?
1、线程共享当前进程的地址空间,且文件不宜过大,否则文件结构过于复杂,同时不能通过memory map函数(Memory Map相当于这样一个数学函数:函数的输入量是地址编码,输出量被寻址单元中的数据)映射到内核中;
2、全局变量以及打开的文件;
3、代码段:线程可以共享进程的代码段;
4、信号:若某一线程发生错误,若该错误未被处理,则内核会将其杀死,故线程的健壮性不强。
Q1:线程健壮性的概念?
A1:健壮性具体指的是线程在不正常的环境下仍能表现出正常的程度。
Q2:线程安全?
A2:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
Q3:如何做到线程安全?
A3:一般说来,确保线程安全的方法有这几个:竞争与原子操作、同步与锁、可重入、过度优化。
- 竞争与原子操作:多个线程同时访问和修改一个数据,可能造成很严重的后果。出现严重后果的原因是很多操作被操作系统编译为汇编代码之后不止一条指令,因此在执行的时候可能执行了一半就被调度系统打断了而去执行别的代码了。一般将单指令的操作称为原子的(Atomic),因为不管怎样,单条指令的执行是不会被打断的。因此,为了避免出现多线程操作数据的出现异常,Linux系统提供了一些常用操作的原子指令,确保了线程的安全。但是,它们只适用于比较简单的场合,在复杂的情况下就要选用其他的方法了。
- 同步与锁 :为了避免多个线程同时读写一个数据而产生不可预料的后果,开发人员要将各个线程对同一个数据的访问同步,也就是说,在一个线程访问数据未结束的时候,其他线程不得对同一个数据进行访问。同步的最常用的方法是使用锁(Lock),它是一种非强制机制,每个线程在访问数据或资源之前首先试图获取锁,并在访问结束之后释放锁;在锁已经被占用的时候试图获取锁时,线程会等待,直到锁重新可用。二元信号量是最简单的一种锁,它只有两种状态:占用与非占用,它适合只能被唯一一个线程独占访问的资源。对于允许多个线程并发访问的资源,要使用多元信号量(简称信号量)。
- 可重入 : 一个函数被重入,表示这个函数没有执行完成,但由于外部因素或内部因素,又一次进入该函数执行。一个函数称为可重入的,表明该函数被重入之后不会产生任何不良后果。可重入是并发安全的强力保障,一个可重入的函数可以在多线程环境下放心使用。
- 过度优化 :在很多情况下,即使我们合理地使用了锁,也不一定能够保证线程安全,因此,我们可能对代码进行过度的优化以确保线程安全。我们可以使用volatile关键字试图阻止过度优化,它可以做两件事:第一,阻止编译器为了提高速度将一个变量缓存到寄存器而不写回;第二,阻止编译器调整操作volatile变量的指令顺序。在另一种情况下,CPU的乱序执行让多线程安全保障的努力变得很困难,通常的解决办法是调用CPU提供的一条常被称作barrier的指令,它会阻止CPU将该指令之前的指令交换到barrier之后,反之亦然。
三、线程独享哪些资源?
1、栈:每个线程的回调函数都需要压栈的操作。
2、计数器:线程是资源调度的基本单位。
3、线程同步需要定义锁资源放至为全局,而线程通讯往往是基于线程同步展开的,我们可以利用的有互斥锁,读写锁,条件变量以及信号量。
- 互斥锁:互斥锁指代相互排斥,它是最基本的同步形式。互斥锁用于保护临界区(上锁与解锁之间的代码称为临界区),以保证任何时刻只有一个线程在执行其中的代码。
如果尝试给一个已由另外某个线程锁住的互斥锁上锁,那个pthread_mutex_lock将阻塞到该互斥锁解锁为止。prhread_mutex_trylock是对应的非阻塞函数,如果该互斥锁已锁住,它就返回一个EBUSY错误。互斥锁保护的是临界区中被操作的数据,也就是说互斥锁通常用于保护由多个线程或多个进程分享的共享数据。互斥锁是协作性锁。这就是说,如果共享一个链表,那么操纵该链表的所有线程都必须在实际操作前获取该互斥锁。
- 条件变量:互斥锁用于上锁,条件变量则用于等待。
使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其他的某个线程改变了条件变量,他将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。(https://blog.csdn.net/sishuiliunian0710/article/details/9625407、https://blog.csdn.net/zrf2112/article/details/52287915)
- 读写锁:ReadWriteLock也是一个接口,提供了readLock和writeLock两种锁的操作机制,一个资源可以被多个线程同时读,或者被一个线程写,但是不能同时存在读和写线程。读写锁相较于互斥锁的优点仅仅是允许读读的并发,除此之外并无其他。某些应用中读数据比修改数据频繁,这些应用可从改用读写锁代替互斥锁中获益。
- 信号量:信号量的本质是一种数据操作锁(一个计数器而已)只是外部资源的标识; 当请求一个使用信号量来表示资源时,进程先要看信号量的值来判断是否可用,大于0就表示可以请求,等于0就是没有资源可用,进程会进入睡眠状态可用直至有资源。信号量其实没有数据交换的功能,他是控制其他通信文件来实现进程间的通信。信号量的作用就是:在任一时刻只能有一个执行线程访问代码的临界区。
四、进程间通讯机制
1、管道:管道是一个线性字节数组,类似文件,使用文件的方式进行访问。管道所占空间可以是内存也可以是磁盘。在Linux中可以使用|来创建管道。在程序里面,创建管道需要使用系统调用popen()或者pipe()。Pipe调用返回两个文件描述符,其中一个用于从管道进行读操作,另一个进行写操作。在使用pipe调用创建管道之后,再使用fork产生两个进程。这两个进程使用pipe返回的文件描述符进行通讯。
2、记名管道:在两个不相关的线程(两个不同进程里面的线程之间进行管道通信。则需要使用有名管道)一个线程通过创建一个记名管道之后,另外一个线程可使用open来打开这个管道。从而与另外一段进行交流。
3、socket
4、信号
使用条件:
在计算机内核里,信号就是一个内核对象,或者说是一个内核数据结构。发送方将该数据的内容填好,并指明该信号的目标进程后,发出特定的软件中断。操作系统接受到特定的中断请求后,知道是有进程发送信号,于是到特定的内核数据结构里查找信号接收方,并进行通知。接收到通知的进程则对信号进行相应处理。 必须先建立连接后,才能通信,相当于打电话。建立连接非常耗时,所以小的通信量使用信号来处理。信号就是进程在收到一个信号的时候,立即作出回应。
5、共享内存: 一个进程首先创建一片内存空间专门作为通信用,而其他进程则将该片内存映射到自己的虚拟地址空间。这样,读写自己地址空间中对应共享内存的区域时,就是在和其他进程进行通信。共享内存机制通信的两个进程必须在同一台物理机器上,其次,共享内存的访问方式是随机的,而不是只能从一端写,另一端读。减少进程切入内核次数。
6、消息队列:新来的消息放在队列尾部,而读取消息则从队列头部开始。它无需固定读写进程,任何进程都可以读写。它可以同时支持多个进程,多个线程可以读写消息队列。