资源竞争

可剥夺资源不可剥夺资源
已经被分配给进程的资源,可以在其所有者尚未主动放弃拥有权限之前被其他进程剥夺,且不会引起系统错误不可剥夺资源是指系统把这类资源分配给进程之后,获得资源的进程一直占有该资源,直到使用完毕后自动释放,否则其他进程无法获取到该资源
比如高优先级的进程可以剥夺低优先级进程的CPU,只要在剥夺之前做好对当前处理及状态的储存,则此次剥夺不会对两个进程的执行结果造成任何影响要求处理连续的工作
CPU和内存都是典型的可剥夺资源磁带机、打印机都属于不可剥夺资源

各进程间的推进顺序不当
进程在运行过程中,请求和释放资源的顺序不当,也可能导致产生进程死锁。

volatile关键字 可见性 顺序性 原子性
向系统申明不允许对内联汇编使用优化

#include "threads.h"
#define nworkers 4

volatile int done=0;
void workload(){
    //wordload
    asm volatile("lock addl $1, %0":"=m"(done)); //内存屏障,由于volatile前后的代码仍然存在重排序
}

void print_done(){
    printf("All done\n");
}

int main(){
    for(int i=0;i<nworkers;i++)
        create(workload);
    while(done!=nworkers);  //这一步中,有对done变量的读取,而这个行为不受保护,也不是一个原子操作,我们本指望等到done=3,可是编译器优化成将done的值存到eax寄存器,而eax寄存器是线程本地的,当寄存器的值不等于4的时候循环,如果4个线程在赋值之前就终止,则就可以正常打印All done,相反,如果赋值的时候,如果不是所有的线程都结束,将会出现死循环;所以为了阻止编译器对done进行优化,把done这次共享内存的读,提到循环之外,我们可以在循环中上一把互斥锁、自旋锁,或者用原子指令每次读入done,我们也可以这样,由于volatile的申明,可以保证C语言中每一个变量都对应到一条指令上,每次都执行对共享内存的读
    printf("All done\n");
}
producer(){
   P(empty);
   P(mutex);
   V(mutex);
   V(full);
}

consumer(){
   P(full);
   P(mutex);
   V(mutex);
   V(empty);
}
//mutex信号量是为了防止某些进程对某些资源的同时访问
//如果存在某种情况,producer中的mutex、empty信号量和consumer中的mutex和full信号量分别颠倒位置
producer(){
   P(mutex);  //1->0 
   P(empty);  //0->-1 阻塞,如何继续执行?需要consumer的V(empty)释放empty信号量
   V(mutex);  //producer在P(empty)就已经阻塞,不会释放mutex信号量
   V(full);
}
//互相等待对方所持有的临界资源,造成死锁
consumer(){
   P(mutex);  //0->-1 阻塞,如何继续执行?需要producer的V(mutex)释放mutex信号量
   P(full);
   V(mutex);
   V(empty);
}

从这个例子,就能看出死锁的成因
1.互斥访问 资源的固有属性
2.不可抢占 资源只能主动放弃
3.请求和保持 进程必须占有,再去申请资源
4.环路 资源循环等待,进程所需要的资源都在环上
四个条件必须同时成立,就比如说如果1、2、3成立而4不成立,则可能会出现所需要的资源并不只有在环上

死锁预防:破坏死锁的条件

破坏环路条件