进程与线程有什么区别?
线程同步有哪些机制?
线程同步指多个线程同时访问某资源时,采用一系列的机制以保证同时最多只能一个线程访问该资源。线程同步是多线程中必须考虑和解决的问题,因为很可能发生多个线程同时访问(主要是写操作)同一资源,如果不进行线程同步,很可能会引起数据混乱,造成线程死锁等问题
- 互斥量:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以可以保证公共资源不会同时被多个线程访问。
过程:
在访问公共资源前对互斥量进行设置(加锁),在访问完成后释放互斥量(解锁)
对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程都会被阻塞,直到当前线程释放该互斥锁。如果释放互斥量时有一个以上的线程阻塞,那么所有阻塞的线程都会变成可运行状态,第一个变为可运行状态的线程就可以对互斥量加锁,其他线程看到互斥量依然是锁着的。
- 信号量(posix信号量):它允许多个线程同一时刻访问同一资源,但是需要限制同一时刻访问此资源的最大线程数目。信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中PV操作相似。
- 条件变量:
条件变量是利用线程间共享的全局变量(即我们设置的条件变量必须是全局变量)进行同步的一种机制。
主要包括两个动作:一个线程等待"条件变量的条件成立"前而挂起;另一个线程使"条件成立"(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。
进程间通信的方式?
两个/多个进程之间数据的交互 即进程间通信,Linux几乎支持全部UNIX进程间通信方法,包括管道(有名管道和无名管道)、消息队列、共享内存、信号量和套接字。其中前四个属于同一台机器下进程间的通信,套接字则是用于网络通信。
面试时回答:
管道:
无名管道(内存文件):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程之间使用。进程的亲缘关系通常是指父子进程关系。
有名管道(FIFO文件,借助文件系统):有名管道也是半双工的通信方式,但是允许在没有亲缘关系的进程之间使用,管道是先进先出的通信方式。
共享内存:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与信号量,配合使用来实现进程间的同步和通信。
消息队列:是用于 两个进程之间的通讯,首先在一个进程中创建一个消息队列,然后再往消息队列中写数据,而另一个进程则从那个消息队列 中取数据。需要注意的是,消息队列是用创建文件的方式建立的,如果一个进程向某个消息队列中写入了数据之后,另一个进程并没有取出数据,即使向消息队列中 写数据的进程已经结束,保存在消息队列中的数据并没有消失,也就是说下次再从这个消息队列读数据的时候,就是上次的数据
套接字:适用于不同机器间进程通信,在本地也可作为两个进程通信的方式。
信号:用于通知接收进程某个事件已经发生,比如按下ctrl + C就是信号。
信号量集(system V):信号量集 就是由信号量组成的数组,信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,实现进程、线程的对临界区的同步及互斥访问。
几种方式的比较:
- 管道:速度慢,容量有限
- 共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了一块内存的。
- 消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题。
- 信号量集:不能传递复杂消息,只能用来同步
消息队列
数据放入消息中,消息放入队列中。
- 相关接口
- 得到key( ftok() )
- 创建/取得 msgid
int msgget(key_t key,int msgflg) //成功返回队列ID,失败返回-1
创建时,msgflg部分可以写IPC_CREAT|IPC_EXCL|0660
取得时,msgflg部分写0即可
- 发送/接收消息 ( msgsnd()/msgrcv())//填充队列
int msgsnd(int msgid,const void*msgp,sizeof(msg),int msgflg)//将msgp消息发送到msqid队列中,失败返回-1
msgp:发送无类型消息时,正常写消息
发送有类型消息时,定义结构体,将消息传入结构体
msgflg: 0 队列满了等待
IPC_NOWAIT 队列满了,返回错误,不等待
ssize_t msgrcv(int msgid,void*msgp,sizeof(msg),long msgtyp,int msgflg)//将msgid队列中的消息读取到msgp中,返回接收的消息字节数
(接收错误(即msgflg设置为 IPC_NOWAIT,没有消息时),返回-1)
msgtyp 为接收消息类型
msgflg: 0:等待消息
IPC_NOWAIT: 没有消息返回错误。
4 如果确定不再使用,可以删除(msgctl())
int msgctl(int msqid,int cmd,struct msqid_ds* buf)//cmd为IPC_RMID删除IPC结构
共享内存
进程可以将同一段共享内存连接到它们自己的地址空间,所有进程都可以访问共享内存中的地址,如果某个进程向共享内存内写入数据,所做的改动将立即影响到可以访问该共享内存的其他所有进程。
相关接口
创建共享内存:int shmget(key_t key, int size, int flag);
成功时返回一个和key相关的共享内存标识符,失败返回范围-1。
key:为共享内存段命名,多个共享同一片内存的进程使用同一个key,使用ftok()函数创建 。
size:共享内存容量。
flag:权限标志位,和open的mode参数一样。
连接到共享内存地址空间:void *shmat(int shmid, void *addr, int flag);
返回值即共享内存实际地址(虚拟内存的首地址)。
shmid:shmget()返回的标识。
addr:可以指定首地址,否则系统默认(写0即可)
flag:访问模式,0默认读写。
- 使用共享内存
从共享内存分离:int shmdt(const void *shmaddr);
调用成功返回0,失败返回-1。
- shmaddr:是shmat()返回的首地址指针。
如果所有的进程都不再使用,则删除共享内存
- 其他补充
共享内存的方式像极了多线程中线程对全局变量的访问,大家都对等地有权去修改这块内存的值,这就导致在多进程并发下,最终结果是不可预期的。所以对这块临界区的访问需要通过信号量来进行进程同步。
但共享内存的优势也很明显,首先可以通过共享内存进行通信的进程不需要像无名管道一样需要通信的进程间有亲缘关系。其次内存共享的速度也比较快,不存在读取文件、消息传递等过程,只需要到相应映射到的内存地址直接读写数据即可。
信号量集
在提到共享内存方式时也提到,进程共享内存和多线程共享全局变量非常相似。所以在使用内存共享的方式时也需要通过信号量来完成进程间同步。多线程同步的信号量是POSIX信号量, 而在进程里使用SYSTEM V信号量。 两种信号量的区别
信号量集的操作步骤
- ftok()得到key
- semget()函数 创建/获取信号量集
- semctl()函数 初始化信号量集,给每个信号量一个最大值
- semop()函数 可以对信号量计数 进行加1/减1
- 如果不再使用信号量集,使用semctl()删除
相关接口
int semget(key_t key,int nsems,int semflg)
返回信号量集的ID(semid)
nsems:信号量集的长度
semflg:权限 IPC_CREAT|IPC_EXCL|0660
如果只获取不创建写0即可
int semctl( semid,信号量下标,SETVAL,最大值)//初始化
int semop(int semid,struct sembuf semoparray[],size_t nops);
semoparray是一个指针,它指向一个信号量操作数组,信号量操作由sembuf结构表示:
struct sembuf{ unsigned short sem_num;//操作信号量的下标 short sem_op;//对信号量的操作方式 short sem_flg;//信号量的操作标记,默认为0 };
sem_op为-1时,计数减1, 为1时,计数加1
sem_op 为0时阻塞,也可以用IPC_NOWAIT代表非阻塞(就是不再等待)
nops信号量操作数组长度
int semctl(int semid,0,IPC_RMID)//删除
int semctl(int semid, int semnum, int cmd, ...);
semnum表示信号量集数组下标,因为是删除操作,直接写0即可
信号量集除了可以提供互斥之外,还可以调度对共享资源的访问。
即一个线程用信号量操作来通知另外一个线程,程序状态中的某个条件已经为真了。(即同步)
辅助命令
ipcs命令用于报告共享内存、信号量和消息队列信息。
-
ipcs -a:列出共享内存、信号量和消息队列信息。
-
ipcs -l:列出系统限额。
-
ipcs -u:列出当前使用情况。
套接字
内存管理有哪几种方式?
什么是虚拟内存?
什么是内存碎片?什么是内碎片?什么是外碎片?
虚拟地址、逻辑地址、线性地址、物理地址有什么区别?
cache替换算法有哪些?
库函数调用与系统调用有什么不同?
静态链接与动态链接有什么区别?
用户态和核心态有什么区别?
用户栈与内核栈有什么区别?
什么是死锁?死锁产生的原因?死锁的必要条件?怎么处理死锁?
解析:
(--)两个或多个进程由于相互等待资源而产生的一种僵持状态,如果没有外力的干预将一直持续这个状态
(--)系统资源不足、相互竞争资源、请求资源顺序不当
(--)死锁产生的四个条件(有一个条件不成立,则不会产生死锁)
互斥条件:一个资源一次只能被一个进程使用
请求与保持条件:一个进程因请求资源而阻塞时,对方获得资源保持不放
不剥夺条件:进程获得的资源,在未完全使用完之前,不能强行剥夺
循环等待条件:若干进程之间形成一种头尾相接的环形等待资源关系
(--)死锁的处理策略可以分为死锁预防、死锁避免、死锁的检测和解除
1、死锁的预防是通过设立一些限制条件,破坏死锁的一些必要条件,让死锁无法发生。
因为互斥是不可改变的,所以只能破坏其他三个条件中的一个来解除死锁
打破不可抢占条件。即允许进程强行从占有者那里夺取某些资源。就是说,当一个进程已占有了某些资源,它又申请新的资源,但不能立即被满足时,它必须释放所占有的全部资源,以后再重新申请。方法:剥夺资源、杀死其中一个线程
打破请求且保持条件。可以实行资源预先分配策略。即进程在运行前一次性地向系统申请它所需要的全部资源。如果某个进程所需的全部资源得不到满足,则不分配任何资源,此进程暂不运行。
打破循环等待条件,实行资源有序分配策略(和死锁避免中的加锁顺序差不多)。采用这种策略,即把资源事先分类编号,按号分配,使进程在申请,占用资源时不会形成环路。所有进程对资源的请求必须严格按资源序号递增的顺序提出。
2、三种用于避免死锁的技术:在使用前进行判断,只允许不会出现死锁的进程请求资源
加锁顺序(进、线程按照一定的顺序加锁)
加锁时限(进、线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
3、死锁检测
主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景,当检测出死锁时,所有线程回退,释放所有锁,一段时间后重试。但一个更好的方案是给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。
银行家算法(避免死锁产生的算法)(ppt)
银行家算法是从当前状态出发,逐个按安全序列检查各客户谁能完成其工作,然后假定其完成工作且归还全部贷款,再进而检查下一个能完成工作的客户,......。如果所有客户都能完成工作,则找到一个安全序列,银行家才是安全的。
进程的几种状态?
解析:
就绪状态:进程已获得除处理机以外的所需资源,等待分配处理机资源
运行状态:占用处理机资源运行,处于此状态的进程数小于等于CPU数
阻塞状态: 进程等待某种条件,在条件满足之前无法执行
(进程各个状态之间互相转换)
①就绪——执行:对就绪状态的进程,当进程调度程序按一种选定的策略从中选中一个就绪进程,为之分配了处理机(计算机系统中存储程序和数据,并按照程序规定的步骤执行指令的部件)后,该进程便由就绪状态变为执行状态;
②执行——阻塞:正在执行的进程因发生某等待事件而无法执行,如IO请求,则进程由执行状态变为等待状态,如进程提出输入/输出请求而变成等待外部设备传输信息的状态,进程申请资源(主存空间或外部设备)得不到满足时变成等待资源状态,进程运行中出现了故障(程序出错或主存储器读写错等)变成等待干预状态等等;
③阻塞——就绪:处于等待状态的进程,在其等待的事件已经发生,如IO完成,资源得到满足或错误处理完毕时,处于等待状态的进程并不马上转入执行状态,而是先转入就绪状态,然后再由系统进程调度程序在适当的时候将该进程转为执行状态;
④执行——就绪:正在执行的进程,因时间片用完而被暂停执行,或在采用抢先式优先级调度算法的系统中,当有更高优先级的进程要运行而被迫让出处理机时,该进程便由执行状态转变为就绪状态。