通俗理解信号量
信号量本质上是一个加锁原语,让等待者进入睡眠状态,直到等待的资源变为空闲。信号量可分为内核信号量和IPC信号量(用于进程间同步),二者都是计数器,用来为多个进程或者线程共享的数据结构提供访问控制。此处主要讲IPC信号量。
若受保护的资源当前可用,则信号量的值为正整数,若资源现当前不可用,则信号量的值为0。当进程试图访问受信号量保护的资源时会把信号的值减1,但是内核会阻塞这个进程直到在这个信号量上的操作产生一个正值。当进程释放受保护的资源时,会把信号量的值加1。如果此时有进程正在休眠等待此信号量,则唤醒这些进程。一般是通过信号来唤醒这些进程,唤醒顺序遵循先进先出(这些操作对线程也适用)。
为了保证信号量工作正常,信号量值的测试以及增减操作都应当是原子操作。并且为实现进程间通信的目的,信号量一般是在内核中实现的(不排除部分项目使用了用户态的信号量,但是大部分用户态的信号量只能实现线程间通信, 用户态的信号量若需要实现进程间通信,还得搭配内存映射)。
一般而言,信号量的初始值可以是任意正值或者零值,该值表明有多少个共享资源单位可以被共享使用。但通常信号量的使用形式是二元信号量。二元信号量主要用于同步场景:消费者使用sem_wait()阻塞等待,直到生产者调用sem_post()唤醒它。此类场景多用于一个主线程等待多个异步任务(异步线程)调用返回。具体实现方法有多种,可以是最暴力的循环sem_wait,也可以是信号量+原子变量实现:由最后一个完成的异步任务(原子变量初始化为异步任务的数量,每一个异步任务返回时减一次原子变量,原子变量自减为0时为最后返回的那个异步任务)调用sem_post()唤醒主线程。
信号量与互斥锁的差别:
任何时刻只能有一个任务(进程、线程或者协程)持有mutex,即mutex的计数永远只能是1。
mutex的上锁者必须负责mutex的解锁,即只能在同一个上下文中加锁和解锁。而信号量的wait和post操作往往是处于不同的任务,不在同一个上下文之间,毕竟信号量就是为了同步而生的。
参考链接:
《unix环境高级编程第三版》
《深入理解Linux内核第三版》
《Linux内核设计与实现》