PV操作是基于信号量模式实现进程互斥,同步的机制; 在OS, CPU,高级语言层面的锁, 消费者生产者模式 均有使用其设计思想; 比如java里就提供有Semaphore(这里牵涉到AQS, CAS是另一个话题),简单知识整理一下;
P操作, V操作, S信号量;
- P操作, 线程对S 执行+1操作; 如果S >= 0 则继续执行,否则线程阻塞;
- V操作, 线程对S执行-1操作,如果S<0 则从被阻塞的线程中挑选一个执行;否则继续执行;
主要原则: - 如果要对一个/类资源同步需要2个信号量(无论有多少线程参与);如果要对一个/类资源互斥,只需要一个信号量(无论多少线程参与);
- P,V,操作一定是成对出现在一个线程操作中,但是并不需要操作的都是同一个信号量;
下面举3个例子;
- 互斥操作,S = 1(S > 0 意味着资源可用); A, B之间就同一个资源互斥;
//线程A
while(true){
P(S);
doSomething1(); //操作资源
V(S);
}
//线程B
while(true){
P(S);
doSomething2();//操作资源;
V(S);
}
- 简单同步操作, 信号量S1(> 0 它代表一资源可用), 信号量S2(> 0 它代表一资源不可用); 初始值 S1 = 0, S2 = 1;
P.S.上面定义信号量似乎显得有点累赘, 因为似乎可以用一个信号量 是否大于小于0去判断资源是否可用
而实际情况是一个信号量是无法表征出所需要的全部状态, 比如S1 = 1, S2 = 1这种case是不存在的
但是S1 = 0, S2 = 0 状态是存在的,它表明资源正在生产中
即对于一个信号量而言只有 > 0 它代表了某种意义, 而小于<= 0 没有意义
//线程A 它是生产者
while(true){
P(S2)
produce();
V(S1);
}
//线程B, 它是消费者
while(true){
P(S1)
consume();
V(S2);
}
- 一个复杂的多生产者生产者 带缓冲区的模式, 它是上面的 同步 和 互斥模式的复合;
假设存在一组生产者线程A, 一组消费者线程B; 和一个共享的产品缓冲器Buffer;
注意这里资源竞争是发生在buffer上,它有3个维度, 插入buffer的竞争, 从buffer取出的竞争, 以及buffer大小; 其中插入,取出是需要互斥的操作,buffer大小控制是同步操作,所以需要4个信号量;
产品本身的生产 和 消费假设不占用任何资源,生产者 和 消费者 可以在插入buffer之前生产好,或者从buffer取出后再消费,这部分就不体现在伪码中
X = 0 (X > 0 意味着缓冲区存在可用产品, n为缓冲器大小);
Y = n (Y > 0 意味着缓冲区不存在可用产品);
S1 = 1(S1 > 0 意味着生产者可以向Buffer添加产品);
S2 = 1(S2 > 0 意味着消费者可以向Buffer提取产品);
input = 0( 0 <= input <= n-1) 当前插入产品的位置;
output =0( 0<= output <= n-1 )当前可消费产品位置;
//线程组A 生产者
while(true){
P(Y);
P(S1); // 与其他生产者进行互斥;
input = (input + 1) mod n; //插入生产的产品, 并将可插入位置前移;
V(S1);
V(X);
}
//线程组B 消费者
while(true){
P(X);
P(S2); //与其他消费者进行互斥;
output = (output + 1) mod n //消费产品,并将可消费位置前移;
V(S2);
V(Y);
}