一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情。
进程通信引发的问题
- 我们知道同一个进程的多个线程会共享该进程的数据,进程与进程之间是不会共享数据的。
- 因此进程之间不知道对方对数据的修改的,当两个进程同时对一个数据进行操作时就有可能会出大问题了。
- 这时就会产生 竞争条件 ,要阻止多个进程同时读写共享数据,就需要用 互斥 来解决这个问题,那么如何设计互斥条件呢?
- 我们把访问共享内存的的程序片段称为 临界区域 。
- 解决临界问题好的方案需满足:①任何两个进程不能同时处于临界区。②不应对 CPU 的速度和数量做任何假设。③临界外运行的程序不得阻塞其他进程。④不得使进程无限期等待进入临界区。
忙等待的互斥方案
严格轮换法
- 见下方代码,整形变量 turn 初始值为 0 ,用于记录该那个进程进入临界区。像这种用于忙等待的锁称为 自旋锁 。但是当两个进程的速度相差较大时,较快的进程就会在 while 循环中等待,知道慢的进程把 turn 设置为对方进程。就违反了条件③临界外运行的程序不得阻塞其他进程。
while () {
while (turn != 0); // 循环
critical_region();
turn = 1;
noncritical_region();
}
while () {
while (turn != 1); // 循环
critical_region();
turn = 0;
noncritical_region();
}
Peterson 解法
- 开始 0 调用 enter_region(),如果 1 不去竞争的话,0 会一直执行到结束。如果 0 和 1 都调用 enter_region(),则谁先执行到 turn = process,并执行到 while 循环,谁就先进入临界区。如果有一个进入到临界区时,另外一个进程会一直等到它离开临界区时,才能够进入临界区。
#define FALSE 0
#define TRUE 1
#define N 2
int turn;
int interested[N];
void enter_region(int process) {
int other = 1 - process; // 另一个进程
interested[process] = TRUE; // 表示感兴趣
turn = process; // 设置标识
while (turn == process && interested[other] == TRUE);
}
void leave_region(int process) {
interested[process] = FALSE; // 表示离开临界区
}
生产者 - 消费者方案
采用信号量
- 这里我们使用的 down() 和 up() 操作是具有原子性的,它能够保证读和写操作是不可分割的,而处理器的 TSL 和 XCHG 指令能够满足我们的这些需求。
- 这里我们用 mutex,empty,full 三个信号量。分别表示是否进入临界区、空槽数量、满槽数量。这些信号量也是能够实现同步的。
#define N 100
typedef int semaphore; // 信号量特殊整数类型
semaphore mutex = 1; // 控制对临界区域的访问
semaphore empty = N; // 空槽数量
semaphore full = 0; // 满槽数量
void producer(void) {
int item;
while (TRUE) {
item = produce_item();
down(&empty); // 如果为 0 就会进入睡眠,否则空槽数量 - 1
down(&mutex); // 进入临界区域
insert_item(item); // 将一个单位数据放入缓冲区空槽
up(&mutex); // 离开临界区域
up(&full); // 满槽数量 + 1
}
}
void consumer(void) {
int item;
while (TRUE) {
down(&full); // 如果为 0 就会进入睡眠,否则满槽数量 - 1
down(&mutex); // 进入临界区域
item = remove_item();
up(&mutex); // 离开临界区域
up(&empty); // 空槽数量 + 1
consumer_item(item); // 从缓冲区中取出一个单位数据
}
}
采用互斥量
- 这里使用的俩个互斥量分别为:解锁 和 加锁,而互斥量又分为:①快速用户区互斥量 futex。②pthread 中的互斥量。
- 快速用户区互斥量:它实现了基本的锁,但是在非必要时不陷入内核,从而改善了性能(陷入内核的花销很大)。
- pthread 互斥量:它时一个提供同步线程的函数库。
#define MAX 1000000000
pthread_mutex_t the_mutex;
pthread_cond_t condc;
pthread_cond_t condp;
int buffer = 0; // 缓冲区
void *producer(void *ptr) {
for (int i = 0; i <= MAX; i++) {
pthread_mutex_lock(&the_mutex); // 互斥使用缓冲区
whule (buffer != 0) pthtread_cond_wait(&condp,&the_mutex);
buffer = i; // 数据放入缓冲区
pthread_cond_signal(&condc); // 唤醒消费者
pthread_mutex_unlock(&the_mutex); // 释放缓冲区
}
pthread_exit(0);
}
void *consumer(void *ptr) {
for (int i = 0; i <= MAX; i++) {
pthread_mutex_lock(&the_mutex); // 互斥使用缓冲区
whule (buffer == 0) pthtread_cond_wait(&condc,&the_mutex);
buffer = 0; // 从缓冲区取出数据
pthread_cond_signal(&condp); // 唤醒生产者
pthread_mutex_unlock(&the_mutex); // 释放缓冲区
}
pthread_exit(0);
}
总结
- 通过对进程通信解决算法的了解与认识,我们就可以加深对一些比较经典的算法的理解。其中的奥妙之处也会不经意之间出现在我们写的代码中!