操作系统 - 进程同步问题的解决方案
进程同步 是指以某种方式协调多个进程,使它们能够互不干扰地运行。当多个进程并发访问共享资源时,可能会导致数据不一致和意外结果。为了解决这些问题,开发了各种同步机制。
进程同步问题有四种解决方案 −
- Locks
- Semaphores
- Monitors
- Interrupt Disable
用于进程同步的 Locks
Locks 是最简单且最常用的同步机制之一。Lock 是一种数据结构,它允许只有一个进程一次访问共享资源。当一个进程想要访问共享资源时,必须首先获取与该资源关联的 lock。如果 lock 已被另一个进程持有,请求进程将被阻塞,直到 lock 被释放。
Locks 可以使用两种技术实现 −
1. 基于软件的 Locks
基于软件的 locks 使用算法来确保互斥访问。它们使用标志、轮转变量或票据来管理对临界区的访问。例如,可以使用轮转变量来指示轮到哪个进程进入临界区。
以下是一些常见的基于软件的 lock 算法 −
- Peterson's Algorithm − 用于实现两个进程之间互斥访问的经典算法。
- Test-and-Set Lock − 使用单个原子指令测试并设置 lock 变量的简单 lock。
- Dekker's Algorithm − Peterson's algorithm 的更复杂版本,也适用于两个进程。
- Bakery Algorithm − 基于令牌的实现系统,适用于两个以上进程。
2. 基于硬件的 Locks
基于硬件的 locks 使用特殊的 CPU 指令来确保互斥访问。这些指令是原子的,即不可中断。常见的基于硬件的 lock 指令包括 −
- Test-and-Set − 一个原子指令,测试 lock 变量并在空闲时设置它。
- Compare-and-Swap − 一个原子指令,将变量的值与预期值比较,如果匹配则用新值替换。
- Fetch-and-Add − 一个原子指令,获取变量的当前值并向其添加指定值。
原子指令是不可分割的操作。一旦原子指令开始,它会在任何其他操作(如另一个进程或中断)发生之前完全执行完毕。这确保了在执行原子指令期间没有其他进程可以干扰。
spinlock 是一种使用忙等待来获取 lock 的 lock 类型。它通常使用 Test-and-Set 或 Compare-and-Swap 等原子指令实现。当进程尝试获取 spinlock 时,它会持续检查 lock 变量,直到它可用。
注意: Semaphores 和 monitors 被称为操作系统级同步原语,而 locks 通常在应用级别实现。
用于进程同步的信号量
Semaphore 是操作系统级别用于控制对共享资源访问的同步机制。信号量是一个非负整数变量,用于信号化资源的可用性。有两种类型的信号量 −
- Binary Semaphore − 只能取两个值 0 和 1 的信号量。它用于实现互斥。
- Counting Semaphore − 可以取非负整数值的信号量。它用于控制对具有多个实例的资源的访问。
信号量支持两种原子操作 - wait() 和 signal()。wait() 操作会递减信号量值,如果值变为负数,则进程被阻塞。signal() 操作会递增信号量值,如果有进程在等待,则其中一个进程被解除阻塞。
Example
在这个例子中,我们创建一个 binary semaphore 来控制对临界区的访问。
#include <iostream>
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
sem_t semaphore;
void* process(void* arg) {
sem_wait(&semaphore); // wait 操作(等待操作)
// critical section(临界区)
// Use std::cout in C++(在 C++ 中使用 std::cout)
std::cout << "Process " << *(int*)arg << " in critical section" << std::endl;
sem_post(&semaphore); // signal operation(信号操作)
return NULL;
}
int main() {
pthread_t threads[5];
int process_ids[5] = {1, 2, 3, 4, 5};
sem_init(&semaphore, 0, 1); // initialize binary semaphore(初始化 binary semaphore)
for (int i = 0; i < 5; i++) {
pthread_create(&threads[i], NULL, process, &process_ids[i]);
}
for (int i = 0; i < 5; i++) {
pthread_join(threads[i], NULL);
}
sem_destroy(&semaphore);
return 0;
}
上述代码的输出将为 −
Process 1 in critical section Process 2 in critical section Process 3 in critical section Process 4 in critical section Process 5 in critical section
用于进程同步的监视器
Monitors 是用于管理对共享资源访问的高级同步机制。监视器是一种抽象数据类型,它 封装了共享数据 以及操作这些数据的过程。
在监视器中,一次只能有一个进程执行一个过程。这样,监视器提供了互斥。监视器还支持条件变量,这允许进程在满足某些条件之前等待。
用于进程同步的中断禁用
Interrupt Disable 是一种低级同步机制。在此,进程在进入其临界区之前禁用所有硬件中断,并在退出临界区后启用它们。这样,在当前进程处于临界区时,没有其他进程能够访问硬件资源。
这种方法 不适用于多处理器 系统,因为在一个处理器上禁用中断并不能阻止其他处理器访问共享资源。
Conclusion
我们讨论了进程同步问题的四种解决方案:locks、semaphores、monitors 和 interrupt disable。Locks 用于应用级别,而 semaphores 和 monitors 是操作系统级别的同步原语。Interrupt disable 是一种低级机制,仅可用于单处理器系统。这些机制中的每一种都有其自身的优缺点,取决于系统的具体要求。