本文已参与「新人创作礼」活动,一起开启掘金创作之路。
进程同步的方式
进程的异步性:各并发执行的进程以各自独立的、不可预知的速度向前推进。
如何解决进程异步的问题,也就是进程同步需要考虑的东西。
-
临界区 对临界资源(一个时间段内只允许一个进程访问)的访问那段代码被称为临界区,为了互斥的访问临界区,每个进程在进入临界区时,都需要先进行检查,查看当前是否可以访问临界区。
-
同步与互斥 同步:多个进程因为合作产生的直接制约关系,使得进程有一定的先后顺序,即需要实现进程同步 互斥:多个进程在同一时刻只有一个进程能进入临界区,就需要各个进程互斥访问临界区
互斥的进程也是存在一个进程依赖另一个进程发出的消息而形成一种制约与协作的关系,因此,互斥是一种特殊的同步,进程互斥和同步可以简称为同步
为了实现对临界资源的互斥访问,需要遵循以下规则:
1、空闲让进(当临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区)
2、忙则等待(当已有进程进入临界区,其它试图进入临界区的进程必须等待)
3、有限等待(对请求访问临界区的进程,应保证能够在有限时间内进入临界区(保证不会饥饿)
4、让权等待(当进程不能进入临界区时,应该立即释放处理机,防止进程忙等)
- 信号量 信号量是一个整型变量,可以对其执行 P 和 V 操作。 P:如果信号量大于零,就对其进行减 1 操作;如果信号量小于等于 0,进程进入 waiting 状态,等待信号量大于零。(wait操作) V:对信号量执行加 1 操作,并唤醒正在 waiting 的进程。(signal操作) 如果信号量只能取 0 或者 1,那么就变成了互斥量,其实也可以理解成加锁解锁操作,0 表示已经加锁,1 表示解锁。
void wait(Semaphore S) { // P操作
S.value--;
if (S.value < 0) {
block(S.L); // 阻塞队头进程
}
}
void signal(Semaphore S) { // V操作
S.value++;
if (S.value <= 0) {
wakeup(S.L); // 唤醒等待队列的队头进程
}
}
-
管程 使用信号量机制实现的生产者消费者问题需要代码做很多控制,十分麻烦,而管程把控制的代码独立出来,使得代码更加简单、不易出错。
管程类似于Java中的类,管程内部拥有相应的数据,和访问操作这些数据的方法。引入管程的目的就是要更方便地实现进程互斥和同步。
※管程有一个重要特性:在一个时刻只能有一个进程使用管程。进程在无法继续执行的时候不能一直占用管程,否则其它进程永远不能使用管程。
管程引入了条件变量以及相关的操作:wait() 和 signal() 来实现同步操作。
对条件变量执行 wait() 操作会导致调用进程阻塞,把管程让出来给另一个进程持有signal() 操作用于唤醒被阻塞的进程。
条件变量是一种抽象数据类型,每个条件变量保存了一个链表,用于记录因该条件变量而阻塞的所有进程
进程通信的方式
进程通信和进程同步很容易混淆,其实可以把进程通信当成一种手段,进程同步是一种目的,为了实现进程同步,可传输一些进程同步所需要的信息。
-
管道
- 匿名管道:这种通信的方式是半双工通信的,同一时间段只能单向传输。并且只能在具有亲属关系的进程之间通信使用,可以看作是只能存在于内存中的一种特殊文件。
- 命名管道:可以用 mkfifo 命令创建一个命名管道,可以用一个进程向管道里写数据,然后可以让另一个进程把里面的数据读出来。命名管道的优点是去除了只能在父子进程中使用的限制,并且命名管道有路径名和它相关联,是以一种特殊设备文件形式存在于文件系统中。
- 没写满就不允许读,没读空就不允许写(数据一旦被读出,就从管道中被抛弃)
-
消息队列
-
消息队列的通信模式是这样的:a 进程要给 b 进程发消息,只需要把消息挂在消息队列(可以是中间实体(信箱),也可以是接收进程自己的消息队列上,分别对应:间接通信方式和直接通信方式)里就行了,b 进程需要的时候再去取消息队列里的消息。
-
消息队列可以独立于读写进程存在,就算进程终止时,消息队列的内容也不会被删除。
-
读进程可以根据消息类型有选择的接收消息,而不像 FIFO 那样只能默认接收。
如果进程发送的数据较大,并且两个进程通信非常频繁的话,消息队列模型就不太合适了,因为如果发送的数据很大的话,意味着发送消息(拷贝)这个过程就需要很多时间来读写内存。
-
-
共享内存
- 共享内存的方式就可以解决拷贝耗时很长的问题了。
共享内存是最快的一种进程通信的方式,因为进程是直接对内存进行存取的。因为可以多个进程对共享内存同时操作,所以对共享空间的访问必须要求进程对共享内存的访问是互斥的。-
所以我们经常把信号量和共享内存一起使用来实现进程通信。
- (这里补个知识!!!系统加载一个进程的时候,分配给进程的内存并不是实际的物理内存,而是虚拟内存空间。那么我们可以让两个进程各自拿出一块虚拟地址空间来,映射到同一个物理内存中。 这样两个进程虽然有独立的虚拟内存空间,但有一部分是映射到相同的物理内存,这样就完成共享机制了)
-
信号量
- 共享内存最大的问题就是多进程竞争内存的问题,就像平时所说的线程安全的问题,那么就需要靠信号量来保证进程间的操作的同步与互斥。
- 信号量其实就是个计数器,例如信号量的初始值是 1,然后 a 进程访问临界资源的时候,把信号量设置为 0,然后进程 b 也要访问临界资源的时候,发现信号量是 0,就知道已有进程在访问临界资源了,这时进程 b 就访问不了了,所以说信号量也是进程间的一种通信方式 (进程互斥访问共享内存空间,一个放数据,一个取数据,就实现了进程间的通信)
-
套接字(不同主机间的进程通信,TCP套接字) 套接字可以实现两个不同的机器之间的进程通信,比如 socket 。
管道是文件,而消息队列是在内存中的,所以管道是外存的,消息队列是内存的