前言
进程(Process)是程序的一次执行;线程(Thread)可以理解为进程中的执行的一段程序片段。
序号 | 对比项 | 进程 | 线程 |
---|---|---|---|
1 | 资源分配 | 操作系统(OS)资源分配的基本单位,每个进程有独立的地址空间和资源,创建和销毁进程的开销较大。 | 处理器(CPU)任务调度和执行的基本单位,共享进程的地址空间和资源,创建和销毁线程的开销较小。 |
2 | 通信与同步 | 进程间通信需要使用特殊的机制(如管道、消息队列、共享内存等)。 | 由于多个线程共享同一个进程的资源,线程之间的通信和同步比进程之间更方便。线程间可以直接读写共享变量。 |
3 | 安全性 | 进程是独立的执行单元,具有自己的调度算法,在并发条件下更加稳定可靠,一个进程崩溃后,不会影响其他进程。 | 由于多个线程共享同一进程的资源,线程之间的操作可能会相互干扰,导致数据一致性问题;一个线程死掉导致整个进程死掉。 |
IPC(Inter-Process Communication)通信机制
一、管道
- 无名管道(pipe)
- 有名管道(FIFO)
序号 | 特点 | 无名管道 | 有名管道 |
---|---|---|---|
1 | 通信模式 | 半双工 | |
2 | 数据写入遵循原则 | 先进先出 | |
3 | 传输数据格式 | 无格式,需要读写双方事先约定 | |
4 | 数据读取 | 一次性读取 | |
5 | 文件性质 | 一种特殊的文件,不属于文件系统,只存在于内存中;在内存中对应一个缓冲区,不同的系统缓冲区不一样大 | |
6 | 适用范围 | 父子进程间通信 | 任意进程间通信 |
7 | 创建管道函数接口 | pipe() | mkfifo() |
二、信号(Signal)
信号是软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式 。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。
信号的特点:1)简单;2)不能携带大量信息;3)满足某个特设条件才发送。
三、消息队列(Message Queue)
消息队列实质为消息的链表,数据存放于内存中,由内核维护。
序号 | 列举项 | 特点 |
---|---|---|
1 | 数据格式 | 消息有数据类型和格式 |
2 | ||
3 | ||
4 | ||
5 |
创建消息队列的步骤如下:
- 获取一个唯一的消息队列的IPC键值
通过ftok()获取键值
函数原型:key_t ftok( char * fname, int id ); - 通过键值获取IPC对象标识符
通过msgget()创建消息队列(IPC对象),并返回消息队列的IPC对象标识符
函数原型:int msgget(key_t key, int msgflg); -
使用消息队列发送消息
使用msgsnd()将消息写入消息队列
函数原型:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg) -
使用消息队列接收消息
使用msgrcv()读取消息队列里的消息
函数原型: ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
四、共享内存(Share Memory)
五、 共享存储映射(Share Memory-mapped)
存储映射I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。 当从缓冲区中取数据,就相当于读文件中的相应字节;将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不使用read和write函数的情况下,使用地址(指针)完成I/O操作。
5.1 存储映射函数常用接口如下:
- mmap函数: 创建内存映射区(将一个文件或其他对象映射进内存)
#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
- munmap函数: 释放内存映射区
#include <sys/mman.h> int munmap(void *addr, size_t length);
5.2 共享映射实现父子进程通信参考示例
int fd = open("xxx.txt", O_RDWR);// 打开一个文件
int len = lseek(fd, 0, SEEK_END);//获取文件大小
// 创建内存映射区
void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED)
{
perror("mmap error");
exit(1);
}
close(fd); //关闭文件
// 创建子进程
pid_t pid = fork();
if (pid == 0) //子进程
{
sleep(1); //演示,保证父进程先执行
// 读数据
printf("%s\n", (char*)ptr);
}
else if (pid > 0) //父进程
{
// 写数据
strcpy((char*)ptr, "i am u father!!");
// 回收子进程资源
wait(NULL);
}
// 释放内存映射区
int ret = munmap(ptr, len);
if (ret == -1)
{
perror("munmap error");
exit(1);
}
六、信号量(Semaphore)
信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。
编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于 0 时,则可以访问,否则将阻塞。
PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。
七、套接字(Socket)
套接字是支持TCP/IP网络通信的基本操作单元,可以让不同主机之间的进程进行通信。
---------------------------------------------------------------------------------------------------------------------------------
后记
八、补充知识点
8.1 可重入函数与不可重入函数
可重入是指一个可以被多个任务同时调用的过程,任务在调用时不必担心数据会因其他任务的调用而修改。
不可重入函数满足下列条件 :
1)函数体内使用了静态的数据结构;
2)函数体内调用了malloc()或者free()函数(谨慎使用堆);
3)函数体内调用了标准I/O函数。
保证函数的可重入性的方法:
1)尽量使用局部变量;
2)对于全局变量要添加保护机制,如:采取关中断、信号量等互斥方法。
8.2 守护进程(Daemon Process)
守护进程(Daemon Process),也就是通常说的 Daemon 进程(精灵进程),是 Linux 中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以d结尾的名字。
守护进程是个特殊的孤儿进程,这种进程脱离终端,以避免被任何终端所产生的信息打断,其在执行过程中的信息也不在任何终端上显示。由于在 Linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。
Linux 的大多数服务器就是用守护进程实现的。比如,Internet 服务器 inetd,Web 服务器 httpd 等。
8.3 死锁(DeadLock)
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁的必要条件
1)互斥条件:某资源只能被一个进程使用,其他进程请求该资源时,只能等待,直到资源使用完毕后释放资源。
2)请求和保持条件:进程已经保持了至少一个资源,并且在等待其他资源时不释放已占有的资源。
3)不可剥夺条件:已分配给进程或线程的资源不能被强制性地剥夺,只能由持有资源的进程或线程主动释放。
4)循环等待条件:存在一个进程或线程的资源申请序列,使得每个进程或线程都在等待下一个进程或线程所持有的资源。
处理死锁的思路
1)预防死锁: 破坏死锁的必要条件;
2)避免死锁:系统在分配资源时根据资源的使用情况提前作出预测,从而避免死锁的发生;
3)检测死锁:运行时出现死锁,能及时发现死锁,把程序解脱出来;
4)解除死锁:发生死锁后,解脱进程,通常撤销进程,回收资源,再分配给正处于阻塞状态的进程。
预防死锁的具体方式
1)破坏请求和保持条件
a) 所有进程开始前,必须一次性地申请所需的所有资源;
b)允许一个进程只获得初期的资源就开始运行,再把运行完的资源释放出来,然后再请求新的资源。
2)破坏不可抢占条件
提出新资源请求不能被满足时,进程必须释放已经保持的所有资源,以后需要时再重新申请。
3)破坏循环等待条件
对系统中的所有资源类型进行线性排序,规定每个进程必须按序列号递增的顺序请求资源。假如进程请求到了一些序列号较高的资源,然后又请求一个序列较低的资源时,必须先释放相同和更高序号的资源后才能申请低序号的资源。多个同类型资源必须一起请求。