Class Semaphore{ int sem; WaitQueue q; } Semaphore::P(){ sem--; //请求资源 if (sem<0){ //当资源不足的时候将线程阻塞 Add this thread t to q; block(t); } } Semaphore::V(){ sem++; //释放资源 if (sem<=0){ //若仍存在阻塞的线程,则释放一个线程 Remove a thread t from q; waitupt(t); } }
信号量的使用
互斥访问:保护临界区互斥访问
条件同步:多线程之间同步
用信号量实现生产者-消费者问题
有界缓冲区的生产者-消费者问题
一个或多个生产者再生成数据后放进了一个缓冲区里
单个消费者从缓冲区取出数据处理
任何时刻只能有一个生产者或消费者可访问缓冲区
要求
任何时刻都只能有一个线程操作(互斥访问)
缓冲区空时:消费者必需等待生产者(条件同步)
缓冲区满时:生产者必需等待消费者(条件同步)
设计
缓冲区空:信号量emptyBuffers
缓冲区满:信号量fullBuffers
互斥访问:互斥锁mutex
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Class BoundedBuffer{ mutex = newSemaphore(1); fullBuffers = newSemaphore(0); emptyBuffers = newSemaphore(n); } BoundedBuffer::Deposit(c){ emptyBuffers->P(); mutex->P(); Add c to the buffer; mutex->V(); fullBuffers->V(); } BoundedBuffer::Remove(c){ fullBuffers->P(); mutex->P(); Remove c to the buffer; mutex->V(); emptyBuffers->V(); }
Class Condition{ int numWaiting = 0; WaitQueue q; } Condition::Wait(lock){ numWaiting++; Add this thread t to q; release(lock); // 解锁后再调用scheduler防止死锁 do_scheduler(); acquire(lock); // 重新申请锁,恢复原状 } Condition::Signal(){ if (numWaiting > 0){ Remove a thread t from q; wakeup(t); numWaiting--; } }
Class BoundedBuffer{ Lock lock; int count=0; Condition full,empty; } BoundedBuffer::Deposit(c){ lock->Acquire(); while (count==n) full.Wait(&lock); // 如果满了就等待消费者消费 Add c to the buffer; count++; empty.Signal(); lock->Release(); // 新的生产者信号,通知消费者可以消费 } BoundedBuffer::Remove(c){ lock->Acquire(); while (count==0) empty.wait(&lock); // 如果空了就等待生产者生产 Remove c from buffer; count--; full.Signal(); lock->Release(); // 新的消费者信号,通知生产者可以生产 }
Signal后的三种选择
Hansen管程
发送方退出管程
规定Signal必须是管程中的过程的最后一个语句
Hoare管程
让被唤醒的线程立即执行,发送方进入Signal队列
如果发送方有其他工作要做,会很麻烦
很难确定没有其他工作要做,因为Signal的实现并不知道其是如何被使用的
Mesa管程
发送方继续执行
易于实现
被唤醒的进程实际执行的时候,条件不一定为真
需要让被唤醒的进程重新回到wait()执行,需要重新判断等待原因条件
信号量与管程对比
信号量
控制对多个共享资源的访问,用于进程/线程间的条件同步
可以并发,取决于sem的初始值
sem表示资源的数量
P操作可能导致阻塞,也可能不阻塞
V操作唤醒其他进程/线程后,当前进程/线程与被唤醒者可以并发执行
管程
一种程序结构,限制同一时刻只有一个线程可以访问临界区
管程内部同一时刻只有一个线程在执行
需要自行判断资源是否可用(等待原因判断)
wait操作一定会造成阻塞
signal操作后,被唤醒的线程是否执行取决于管程的风格
屏障
线程A和线程B希望在某一个特定的点交会并继续执行
屏障原语(Barrier)
功能
协调多个进程并行共同完成某项任务
设定一个屏障变量b以及初始值n
如果屏障变量值小于n,则线程等待
如果屏障变量的值达到n,则唤醒所有线程,所有的线程继续工作
屏障实现
使用两个信号量(两个线程时)
初始化两个信号量均为0
使用管程实现(超过两个线程时)
等待原因:抵达屏障的线程数量没有达到n
同步机制
操作系统提供的同步机制
image-20221020103532408
读者-写者问题
共享数据的两类使用者
读者:只读取数据,不进行修改
写者:读取并修改数据
对共享数据的读写
“读者-读者”允许
同一时刻,允许有多个读者同时读
“读者-写者”互斥
没有写者的时候读者才可以读
没有读者的时候写者才可以写
“写者-写者”互斥
没有其他写者的时候写者才可以写
用信号量解决读者-写者问题
用信号量描述每一个约束
信号量WriteMutex
控制对于读写操作的互斥
初始化为1
读者数量Rcount
描述正在进行读操作的读者数目
初始化为0
信号量CountMutex
控制对读者计数的互斥修改
初始化为1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// Writer Process P(WriteMutex); //写写互斥 Write; V(WriteMutex);
// Reader Process P(CountMutex); if (Rcount==0) P(WriteMutex); //读写互斥 ++Rcount; V(CountMutex);
read;
P(CountMutex); --Rcount; if (Rcount==0) V(WriteMutex); //读写互斥 V(CountMutex);
用管程解决读者-写者问题
管程封装
两个基本方法
1 2 3 4 5 6 7 8 9 10
Database::Read() { Wait until no writers; read database; check out – wake up waiting writers; } Database::Write() { Wait until no readers/writers; write database; check out – wake up waiting readers/writers; }
管程的状态变量
1 2 3 4 5 6 7
AR = 0; // of active readers AW = 0; // of active writers WR = 0; // of waiting readers WW = 0; // of waiting writers Lock lock; Condition okToRead; Condition okToWrite;