信号量与互斥锁都是用于多线程编程中,以实现资源共享和线程同步的机制,但它们在应用场景、实现方式和性能特点上有所不同。以下是详细介绍:

  • 应用场景。信号量主要用于线程同步,其核心思想是控制对共享资源的访问许可,当资源可用时,允许线程继续操作;当资源被占用时,线程则阻塞直到资源变得可用。而互斥锁主要用于线程互斥,确保同一时刻只有一个线程能访问特定的资源,防止资源被多个线程同时访问。
  • 实现方式。信号量的值可以是任意非负整数,这表示了可用资源的数量。当信号量的值大于0时,表示有可用资源,线程可以继续操作;当信号量的值为0时,表示没有可用资源,线程需要阻塞直到资源变得可用。而互斥锁的值通常只能为0或1,表示资源是否被锁定
  • 性能特点。信号量不仅用于资源同步,还可以用于进程间通信,而互斥锁仅用于线程间通信。互斥锁在锁定资源时,所有试图访问该资源的线程都会被阻塞,直到资源被解锁。而信号量在资源被锁定时,允许其他线程继续执行某些任务,直到资源被释放。

        信号量可用于进程通信和线程通信,而互斥锁只能用于线程通信。

互斥锁:

  互斥锁是一种保护机制。上锁后其他线程不能进入保护区域的代码,直到锁被释放。

mutex函数在内核文件include\linux\mutex.h中声明,如下表:


1.mutex 计数值只能为 1,也就是说最多允许一个线程访问临界区。

同一时间只能有一个任务持有互斥锁,而且只有这个任务可以对互斥锁进行解锁。

2.必须在同一个上下文加锁和解锁。

3.不能递归的上锁和解锁。

4.持有 mutex 时,进程不能退出。

5.mutex 不能在中断或者下半部使用,只能在进程上下文中使用。

6.mutex 只能通过官方 API 管理,不能自己写代码操作它

mutex的结构体定义如下:

它里面有一项成员“struct task_struct *owner”,指向某个进程。一个mutex只能在进程上下文中使用:谁给mutex加锁,就只能由谁来解锁。

而semaphore并没有这些限制,它可以用来解决“读者-写者”问题:程序A在等待数据──想获得锁,程序B产生数据后释放锁,这会唤醒A来读取数据。semaphore的锁定与释放,并不限定为同一个进程。

信号量:

        信号量也是一种锁,和自旋锁不同的是,线程获取不到信号量的时候,不会像自旋锁一样循环区试图获取锁,而是进入睡眠,直至有信号量释放出来时,才会唤醒睡眠的线程,进入临界区执行。

        由于使用信号量时,线程会睡眠,所以等待的过程不会占用 CPU 时间。所以信号量适用于等待时间较长的临界区。
        信号量消耗 CPU 时间的地方在于使线程睡眠和唤醒线程。

        信号量(semaphore)的数据结构为一个值和一个指针,指针指向等待该 信号量的下一个进程。信号量的值与相应资源的使用情况有关。注意,信号量的值仅能由PV操作来改变。

        可以发现信号量结构体中有个自旋锁,这个自旋锁的作用是保证信号量的down和up等操作不会被中断处理程序打断。

 p操作和v操作

        p操作和v操作是不可中断的程序段,称为原语。P,V原语中P是荷兰语的Passeren,相当于英文的pass, V是荷兰语的Verhoog,相当于英文中的incremnet。且在P,V原语执行期间不允许有中断的发生。

首先应弄清PV操作的含义:PV操作由P操作原语和V操作原语组成(原语是不可中断的过程),对信号量进行操作,具体定义如下:

P(S):①将信号量S的值减1,即S=S-1;②如果S>=0,则该进程继续执行;否则该进程置为等待状态,排入等待队列。

V(S):①将信号量S的值加1,即S=S+1;②如果S>0,则该进程继续执行;否则释放队列中第一个等待信号量的进程。

PV操作的意义:

        我们用信号量及PV操作来实现进程的同步和互斥。PV操作属于进程的低级通信。

对信号量有4种操作:

1. 初始化(initialize),int set_init(sem_t *sem, int pshared, unsigned int value);//第二参数为0表示进程间不共享
2. 等信号(wait),int sem_wait(sem_t *sem);//信号量大于1时,减一并返回;小于1时线程阻塞。
3. 给信号(signal)int sem_post(sem_t *sem);//信号量加一
4. 清理(destory) int sem_destory(sem_t *sem);

        初始化semaphore之后,就可以使用down函数或其他衍生版本来获取信号量,使用up函数释放信号量。我们只分析down、up函数的实现。

down函数的实现:

如果semaphore中的count大于0,那么down函数就可以获得信号量;否则就休眠。在读取、修改count时,要使用spinlock来实现互斥。

休眠时,要把当前进程放在semaphore的wait_list链表中,别的进程释放信号量时去wait_list中把进程取出、唤醒。

代码如下:

up函数的实现:

        如果有其他进程在等待信号量,则count值无需调整,直接取出第1个等待信号量的进程,把信号量给它,共把它唤醒。

如果没有其他进程在等待信号量,则调整count。整个过程需要使用spinlock来保护,代码如下:

 semaphore和mutex的区别

        信号量是一种同步机制。semaphore中可以指定count为任意值,当值大于0代表有可用资源,则允许继续操作,否则线程阻塞,等待可用资源。比如有10个厕所,所以10个人都可以使用厕所。
        而mutex的值只能设置为1或0,只有一个厕所。如果资源大于1时使用互斥锁,则就算资源数大于1时,也只能有一个线程进入操作,其余线程必须阻塞。

是不是把semaphore的值设置为1后,它就跟mutex一样了呢?

        不是的。 Semaphore与Mutex在实现上有一个重大的区别:ownership。即互斥锁的所有权特性。Mutex被持有后有一个明确的owner,而Semaphore并没有owner,当一个进程阻塞在某个信号量上时,它没法知道自己阻塞在哪个进程(线程)之上。


  没有ownership会带来以下几个问题:

        在保护临界区的时候,无法进行优先级反转的处理;

        系统无法对其进行跟踪断言处理,比如死锁检测等;

        信号量的调试变得更加麻烦;

        因此,在Mutex能满足要求的情况下,优先使用Mutex。

两者主要区别如下表所示:


区别在于 mutex 只能被同一线程加锁解锁,二值信号量可以被不同线程加锁解锁。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐