并发-信号量
2023-1-13
| 2023-8-2
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
Property
 
 
可以使用信号量作为锁和条件变量
信号量是有一个整数值的对象,可以用两个函数来操作它。在POSIX标准中,是sem_wait()sem_post()。因为信号量的初始值能够决定其行为,所以首先要初始化信号量,才能调用其他函数与之交互:
信号量初始化之后,可以调用sem_wait()sem_post()与之交互
sem_wait()要么立刻返回(调用sem_wait()时,信号量的值大于等于1),要么会让调用线程挂起,直到之后的一个post操作。当然,也可能多个调用线程都调用sem_wait(),因此都在队列中等待被唤醒。
sem_post()并没有等待某些条件满足。它直接增加信号量的值,如果有等待线程,唤醒其中一个
当信号量的值为负数时,这个值就是等待线程的个数
 

二值信号量(锁)

信号量的第一种用法:用信号量作为锁。
接把临界区用一对sem_wait()/sem_post()环绕,为了使这段代码正常工作,信号量m的初始值是1。
假设有两个线程的场景。第一个线程(线程0)调用了sem_wait(),它把信号量的值减为0。然后,它只会在值小于0时等待。因为值是0,调用线程从函数返回并继续,线程0现在可以自由进入临界区。线程0在临界区中,如果没有其他线程尝试获取锁,当它调用sem_post()时,会将信号量重置为1(因为没有等待线程,不会唤醒其他线程)。
notion image
如果线程0 持有锁(即调用了sem_wait()之后,调用sem_post()之前),另一个线程(线程1)调用sem_wait()尝试进入临界区。这种情况下,线程1把信号量减为−1,然后等待(自己睡眠,放弃处理器)。线程0 再次运行,它最终调用sem_post(),将信号量的值增加到0,唤醒等待的线程(线程1),然后线程1 就可以获取锁。线程1 执行结束时,再次增加信号量的值,将它恢复为1。
notion image
除了线程的动作,表中还显示了每一个线程的调度程序状态:运行、就绪(即可运行但没有运行)和睡眠。特别要注意,当线程1尝试获取已经被持有的锁时,陷入睡眠。只有线程0 再次运行之后,线程1 才可能会唤醒并继续运行
 

信号量用作条件变量

信号量也可以用在一个线程暂停执行,等待某一条件成立的场景。例如,一个线程要等待一个链表非空,然后才能删除一个元素。在这种场景下,通常一个线程等待条件成立,另外一个线程修改条件并发信号给等待线程,从而唤醒等待线程。因为等待线程在等待某 些条件发生变化,所以将信号量作为条件变量。
假设一个线程创建另外一线程,并且等待它结束:
有两种情况需要考虑。第一种,父线程创建了子线程,但是子线程并没有运行。这种情况下,父线程调用sem_wait()会先于子 线程调用sem_post()。我们希望父线程等待子线程运行。为此,唯一的办法是让信号量的值不大于0。因此,0 为初值。父线程运行,将信号量减为−1,然后睡眠等待;子线程运行的时候,调用sem_post(),信号量增加为0,唤醒父线程,父线程然后从sem_wait()返回,完成该程序。
notion image
第二种情况是子线程在父线程调用sem_wait()之前就运行结束。在这种情况下,子线程会先调用sem_post(),将信号量从0 增加到1。然后当父线程有机会运行时,会调用sem_wait(),发现信号量的值为1。于是父线程将信号量从1 减为0,没有等待,直接从 sem_wait()返回,也达到了预期效果。
notion image
 

实现信号量

用底层的同步原语(锁和条件变量),来实现自己的信号量,名字叫作Zemaphore
 
  • 计算机基础
  • 操作系统
  • 并发-条件变量并发-常见并发问题
    目录