0x01 信号量
定义: 在计算机科学中,信号量是一种变量或抽象数据类型,用于控制多线程对公共资源的访问,避免并发系统(如多任务操作系统)中的临界区问题。一个平凡的信号量是一个普通的变量,它根据程序员定义的条件而改变(例如,增加或减少,或切换)。
0x02 初始化
在操作之前我们需要初始化信号量
#include <semaphore.h>
sem_t s;
sem_init(&s, 0, 1);
sem_init 有三个参数,第一个是变量地址,第二个为 0 代表同一进程,多个线程共享,第三个是初始值
0x03 操作
在 POSIX 标准,由两组 API 界定操作:sem_wait()和 sem_post()
sem_wait() 代表线程挂起等待,要么立刻返回(大于等于 1 时),直到调用 post,但也会有多个线程调用 wait,对于多个不满足条件的线程,都将会进入等待队列
sem_post() 代表如果等待队列有等待线程,则唤醒其中一个,如果信号量为负数,代表有多少个线程在等待中
0x04 应用
信号量的应用很广泛,这里例举两个经典场景,锁 和 条件变量
-
锁
- 我们知道锁有两个状态,已占有,非占有,则我们使用信号量来表示,为 0/1 界定,sem_wait(-1)、sem_post(+1) 环绕的就是受保护的临界区
sem_t m; sem_init(&m, 0, 1); sem_wait(&m); // xxx code sem_post(&m);
信号量作为锁,初始值要设定为 1 信号量作为锁,要注意作用域,作用域不适当会照成死锁
-
条件变量
sem_t s; void * child(void *arg) { printf("child\n"); sem_post(&s); // signal here: child is done return NULL; } int main(int argc, char *argv[]) { sem_init(&s, 0, X); // what should X be? printf("parent: begin\n"); pthread_t c; Pthread_create(c, NULL, child, NULL); sem_wait(&s); // wait here for child printf("parent: end\n"); return 0; }
-
如果我们想输出成这样的效果,初始值应该为多少呢 ?
// parent: begin
// child
// parent: end
如果要满足这样的条件,必须满足父线程等待子线程执行完成情况, 我们需要考虑两种情况
-
第一种,父线程创建了子线程,但子线程并没有运行,如果初始值是 1,wait 之后, 父线程继续执行,子线程可能运行在父线程 wait 之前,也可能是之后,如果初始值为 0, 则此时 父线程 wait 只能等待, 子线程 post 才会唤醒父线程
-
第二种,子线程在父线程调用 sem_wait()之前就运行结束,由于这种情况都满足输出结果,故不用考虑
0x05 总结与抽象
-
信号量可以实现锁和条件变量,反过来锁和条件变量也可实现信号量
-
信号量思想: 如果抽象出信号量思想,我会与数轴相联系,都是对数的界定划分,使得数的定义划分开来,从而达到我们想要的效果
-
处理并发时,有时候简单原则更好,需要有锁的时候用锁,信号量虽然能实现效果,但需要从信号量 -> 锁,多了一个步骤,不如直接锁来的语义明确
-
数据库最大连接数,通过信号量也可以实现。
0x06 引用
- WIKI
- 操作系统导论