如需了解进程相关的详细概念,可以移步到我的另一个博客。
操作系统之进程管理
进程的互斥
进程的互斥是指在并发编程中,确保多个进程或线程在同一时间内不能同时访问共享资源或临界区。互斥的主要目的是避免竞态条件,从而保证数据的一致性和系统的稳定性。
还是上面的装箱的例子,箱子就是共享资源,两个人不能同时对箱子进行操作。
我们把一个时间段内只允许一个进程使用的资源称为临界资源。许多物理设备(比如摄像头、打印机)都属于临界资源。此外还有许多变量、数据、内存缓冲区等都属于临界资源。对临界资源的访问,必须互斥地进行。互斥,亦称间接制约关系。进程互斥指当一个进程访问某临界资源时,另一个想要访问该临界资源的进程必须等待。当前访问临界资源的进程访问结束,释放该资源之后,另一个进程才能去访问临界资源。
进程互斥的软件实现方法
单标志法
设置一个标识,将首次执行的权利给其中一个进程,当这个进程执行完后,将此标识设置为下一个进程可以执行的标识。
标识
int turn = 0; // turn表示当前允许进入临界区的进程号
P0进程
while(turn != 0); // 进入区
critical section; // 临界区
turn = 1; // 退出区
remainder section; // 剩余区
P1进程
while(turn != 1); // 进入区
critical section; // 临界区
turn = 0; // 退出区
remainder section; // 剩余区
优点:实现简单;
缺点:只能按照p0->p1->p0->p…这种顺序执行,如果p0进程程将执行权让出去后,p1进程没有运行,p0线程就会一直处于等待状态。因此,单标志法存在的问题是:违背“空闲让进”原则。
双标志先检查
设置一个bool数组类型的标识。当一个进程想要执行的时候,先检查另一个进程有没有想要执行。如果另一个进程没有想要执行(即false),就可以将自己的标识设置为想要执行的状态(即true),执行完成后将自己的状态设置为不想再执行。如果另一个进程想要执行,就等待。
标识
bool flag[2];
flag[0] = false;
flag[1] = false;
P0进程
while(flag[1]); //① 先看看P1进程有没有访问临界区的意愿,如果有,就等待。
flag[0] = true; //② 标记为本进程
critical section; //③ 临界区
flag[0] = false; //④ 退出区
remainder section; // 剩余区
P1进程
while(flag[0]); //⑤ 先看看P0进程有没有访问临界区的意愿,如果有,就等待。
flag[1] = true; //⑥ 标记为本进程
critical section; //⑦ 临界区
flag[1] = false; //⑧ 退出区
remainder section; // 剩余区
优点:能够避免“空闲让进”的原则。
缺点:存在并发问题,如果程序按照①⑤②⑥的顺序执行的话,两个进程就会同时进入临界区。
双标志后检查
标识
bool flag[2];
flag[0] = false;
flag[1] = false;
P0进程
flag[0] = true; //① 先看看P1进程有没有访问临界区的意愿,如果有,就等待。
while(flag[1]); //② 标记为本进程
critical section; //③ 临界区
flag[0] = false; //④ 退出区
remainder section; // 剩余区
P1进程
flag[1] = true; //⑤ 先看看P0进程有没有访问临界区的意愿,如果有,就等待。
while(flag[0]); //⑥ 标记为本进程
critical section; //⑦ 临界区
flag[1] = false; //⑧ 退出区
remainder section; // 剩余区
优点:能够避免“空闲让进”与同时进入临界区的情况。
缺点:存在并发问题,如果程序按照①⑤②⑥的顺序执行的话,就会出现死锁的情况。
Peterson 算法
结合双标志法和单标志法的算法。
- 进程先表示自己想要进入临界资源
- 进程表示谦让,将执行权赋予另一个进程。
- 判断另一个进程是否想要进入临界资源,且执行权是不是在另一个进程,如果是,就等待;如果不是,就访问临界资源
- 访问临界资源
- 退出临界区
标识
bool flag[2];
flag[0] = false;
flag[1] = false;
int turn = 0;
P0进程
flag[0] = true; //① 表示自己想要执行
turn = 1; //② 将执行权先转让给P1进程
while(flag[1] && turn == 1); //③ 判断另一个进程有没有在执行
critical section; //④ 临界区
flag[0] = false; //⑤ 退出区
remainder section; // 剩余区
P1进程
flag[1] = true; //⑥ 表示自己想要执行
turn = 0; //⑦ 将执行权先转让给P0进程
while(flag[0] && turn == 0); //⑧ 判断另一个进程有没有在执行
critical section; //⑨ 临界区
flag[1] = false; //⑩ 退出区
remainder section; // 剩余区
按照①⑥②⑦③⑧的顺序执行,
- P0表示自己想要执行
- P1表示自己想要执行
- P0先谦让
- P1后谦让
- P0检查到P1想要执行单执行权并没有在P1,自己就执行了
- P1检查到执且P0想要执行且执行权在P0,就只能等待
进程互斥的硬件实现方法
-
中断屏蔽(Disable Interrupts):
在单处理器系统中,可以通过在临界区代码运行之前禁用中断来实现互斥。这种方法简单有效,但是不适用于多处理器系统,因为它只能影响单处理器。
-
测试并设置(Test And Set):有的地方也称之为TSL(Test And Set Lock)。
这是一种硬件提供的原子性操作,常常用于实现互斥锁,它可以在一条指令内测试一个标志位的值并将其设置为一个新值。如果一个测试的标志位为空闲状态,则可以将其值设置为1,并且进程可以进入临界区,否则进程将被阻塞等待。
这种算法的容易导致忙等待,当进程持有锁的时候会持续占用CPU执行时间。
以下是C语言的模拟逻辑:
bool TestAndSwap(bool *lock){ bool old = *lock; // 先将旧值存储 *lock = true; // 在将当前锁设置为已上锁状态 return old; // 返回旧值 }
使用该指令的逻辑:
// 锁 bool lock = false; // 假设此方法会有多个线程调用 void test(){ while(TestAndSwap(&lock)); 临界区代码... // 访问临界资源 lock = false;// 解锁 其他代码... // 临界区外的代码 }
-
交换指令(Swap):
有的地方也叫 Exchange 指令,或简称 XCHG 指令。
Swap 指令是用硬件实现的,执行的过程不允许被中断,只能一气呵成。以下是用C语言描述的逻辑
Swap指令
void Swap(bool *a ,bool *b){ bool tmp = *a; *a = *b; *b =tmp; }
使用代码:
bool lock = false; // 假设此方法有多个线程调用 void test(){ bool old = true; while(old == true){ Swap(&old,&lock) } 临界区代码... // 访问临界区代码 lock = false; // 解锁 临界区外的代码... // 访问临界区外的代码 }
逻辑上来看 swap 和 TSL 并无太大区别,都是先记录下此时临界区是否已经被上锁(记录在 old 变量上),再将上锁标记 lock 设置为 true,最后检査 old,如果 old 为false,则说明之前没有别的进程对临界区上锁,则可跳出循环,进入临界区。
优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞:适用于多处理机环境
缺点:不满足“让权等待”原则,暂时无法进入临界区的进程会占用CPU并循环执行TSL指令,从而导致“忙等”。
互斥锁
概念
解决临界区最简单的工具就是互斥锁(mutexlock)。一个进程在进入临界区时应获得锁;在退出临界区时释放锁。函数 acquire()获得锁,而函数 release()释放锁。
每个互斥锁有一个布尔变量 available,表示锁是否可用。如果锁是可用的,调用 acquire()会成功,且锁不再可用。当一个进程试图获取不可用的锁时,会被阻塞,直到锁被释放。
acquire函数
void acquire(){
while(!available){}
available = false;
}
release函数
void release(){
available = true;
}
acquire()或 release()的执行必须是原子操作,因此互斥锁通常采用硬件机制来实现。
互斥锁的主要缺点是忙等待,当有一个进程在临界区中,任何其他进程在进入临界区时必须连续循环调用 acquire()。当多个进程共享同一 CPU时,就浪费了 CPU周期。因此,互斥锁通常用于多处理器系统,一个线程可以在一个处理器上等待,不影响其他线程的执行。
需要连续循环忙等的互斥锁,都可称为自旋锁(spinlock),如TSL指令、swap指令、单标志法